Event API
The Event API provides a flexible mechanism for receiving and publishing events. It enables you to receive information regarding broadcast state, updates to models across the Live and Layout APIs and also as a mechanism for distributing events between clients, backends and the compositor.
JavaScript client
Where possible we suggest using our Javascript client where we handle the low-level implementation for you. Implementation details for interacting directly with the websocket are also provided below if you're using GRPC bindings or the REST API.
Subscribing to custom events:
Custom events are untyped events you're free to distribute from your backend which are consumable by clients or code written for composition.
Subscribing
When subscribing, you can pass an exact event name or a wildcard to match multiple events. You're able to define a target for a layout, project or collection however the incoming event must include all of the matching properties for you to receive it.
// Listen for a specific event.
api.EventApi().subscribe('my_custom_event', {
target: { projectId: 'my-project' },
}).then(() => { /* success */ }).catch((err) => { /* something went wrong - e.g missing permission */ });
// Listen for multiple events using the wildcard.
api.EventApi().subscribe('my_app:*', {
target: { projectId: 'my-project' },
}).then(() => { /* success */ }).catch((err) => { /* something went wrong - e.g missing permission */ });
Handling events
api.EventApi().on('event', {
// A list of events this event handler should handle. This does not have to be a 1:1 mapping with subscribe calls.
// e.g, you could listen for a specific event on my_app:chat here.
name: ['my_custom_event', 'my_app:*'],
// Every instance of the API.stream SDK has a session ID - any Layout API changes, Live API changes or custom events published by this SDK can optionally be ignored.
ignoreSessionEvents: false,
// If `ignoreSessionEvents` is true, and `name` has a wildcard, you can optionally allow specific events to pass through.
allowedSessionEvents: []
}, event => {
console.log(event?.name);
});
Subscribing and unsubscribing from layout events:
The Layout API exposes methods for you to listen to changes on the Layout and Layer models. You can currently listen on a per-layout basis.
Subscribing to a layout
// to subscribe:
api.LayoutApi().subscribeToLayout(A_LAYOUT_ID)
.then(() => { /* success */ })
.catch((err) => { /* something went wrong - e.g missing permission to the layout? */ });
// to unsubscribe:
api.LayoutApi().unsubscribeFromLayout(A_LAYOUT_ID)
.then(() => { /* success */ })
.catch((err) => { /* something went wrong - e.g missing permission to the layout? */ });
Receiving events
// EVENT_TYPE_LAYER can also be EVENT_TYPE_LAYOUT to receive changes for a layout.
api.LayoutApi().on(LayoutApiModel.EventType.EVENT_TYPE_LAYER, (evt) => {
if (evt.create) {
console.log('created', evt.create);
}
if (evt.update) {
console.log('updated', evt.update);
}
if (evt.delete) {
console.log('deleted', evt.delete);
}
});
Subscribing and unsubscribing from live api events:
You can subscribe to changes on most Live API models.
Subscribing to a project's event
// to subscribe
api.LiveApi().subscribeToProject(COLLECTION_ID, PROJECT_ID);
// to unsubscribe:
api.LiveApi().unsubscribeFromCollection(COLLECTION_ID, PROJECT_ID)
.then(() => { /* success */ })
.catch((err) => { /* something went wrong - e.g missing permission to the layout? */ });
Receiving events
// In this example we're fetching Project events however collection, source and destinations are also available.
api.LiveApi().on(LiveApiModel.EventType.EVENT_TYPE_PROJECT, proj => {
if (proj?.create) {
console.log('project created', proj?.create);
}
if (proj?.update) {
console.log('project configuration updated', proj?.update);
}
if (proj?.delete) {
console.log('project deleted', proj?.delete);
}
if (proj?.state) {
// State changes regarding a broadcast, e.g starting, stopping,
console.log('broadcast state update', proj?.state);
}
})
Request Metadata
Layout API requests all provide the option to include a "requestMetadata" field where you can include properties that only exist for the lifetime of the request (and won't be persisted). This should be data that generally instructs why the action occurred.
For example, when creating a layer you can include custom metadata:
import { ApiStream, LayoutApiModel } from "@api.stream/sdk";
const api = new ApiStream({ /* */ });
// Create the new layer
const layer = await api.LayoutApi().layer.createLayer({
layoutId: LAYOUT_ID,
layer: {
type: 'image',
parentId: ROOT_LAYER_ID,
data: { /* */ },
requestMetadata: {
reason: 'user_left',
},
},
});
// In another session, listen for the create event:
api.LayoutApi().subscribeToLayout(LAYOUT_ID);
api.LayoutApi().on(LayoutApiModel.EventType.EVENT_TYPE_LAYER, (evt) => {
if (evt.create) {
console.log(evt.create.requestMetadata) // { reason: 'user_joined' }
}
})
Raw implementation
If you're unable to use the API client or are using a language we don't offer client SDKs for, you can communicate with the events API directly. The following concepts also apply when communicating directly via GRPC bindings. You can use our SDK implementation as a reference: JS Event API implementation
On the wire, the Event API implements JSON-encoded Protobuf. You can find the raw GRPC documentation here
Correlation ID
The correlation ID is a mechanism built into the Event API allowing you to correlate a payload sent with the response coming back (whether a successful payload or an error). You should provide a unique value for every payload you send to the Event API and check incoming payloads to see whether the action was successful. A simple implementation of this is using a UUIDv4.
For example, you may send a payload:
{
"ping": "init",
"correlationId": "8de91274-9e27-4eec-825d-b7df589a729e"
}
In response, you will either get back a successful payload:
{
"result":{
"correlationId": "8de91274-9e27-4eec-825d-b7df589a729e",
"sessionId": "402988e6-4941-4f9e-b841-a5c5ac15a427",
"pong": "init"
}
}
or a payload indicating an error:
{
"result":{
"correlationId": "8de91274-9e27-4eec-825d-b7df589a729e",
"sessionId": "402988e6-4941-4f9e-b841-a5c5ac15a427",
"error": {
"code": 0,
"message": "This is an example error"
}
}
}
Authentication
Authenticating with the websocket is done by sending the header Sec-Websocket-Protocol
, with the a value of Bearer, <JWT>
. You can only authenticate with a websocket when you have a user-issued JWT.
tip
You should also include the sessionid
query parameter with a unique value denoting an instance of this connection. When used in combination with the metadata header SessionID
for Layout API requests, you can de-duplicate requests you know about.
For example, using the native Websocket implementation in Javascript:
const websocket = new Websocket( `https://live.api.stream/eapi/v2/stream?sessionid=12345678`, [
'Bearer', 'YOUR_ACCESS_TOKEN'
]);
Ping-pong
In order to maintain a healthy connection, you should issue a PING request and expect a PONG from the Event API on a periodic interval. You should also do a handshake ping-pong when the connection first opens before you send any payloads.
The following should be accounted for:
- Send a
PING
at an interval of no less than 20 seconds - Wait for a maximum of 10 seconds for a
PONG
response before disconnecting the session. - Use
correlationId
with a unique value (typically a UUID) to ensure the correctPONG
was received. - When you lose connection, maintain an exponential back-off to protect our services.
Sending a ping
{
"ping": "1648220176122", // Unix timestamp in milliseconds of when the ping was sent
"correlationId": "8de91274-9e27-4eec-825d-b7df589a729e", // A unique UUID for every instance of PING.
}
Receiving a pong
The format of a PONG payload looks like the following:
{
"result":{
"correlationId": "8de91274-9e27-4eec-825d-b7df589a729e",
"sessionId": "402988e6-4941-4f9e-b841-a5c5ac15a427",
"pong": "1648220176122"
}
}
Receiving events
Event subscriptions allow you to receive events from multiple sources. This includes Live API updates, Layout API updates and any custom events you may choose to distribute.
Events can be scoped to specific targets. The high-level targets we support are:
collectionId
projectId
layoutId
Subscribing
Subscribing to an event is done by sending the subscribe
payload. To unsubscribe from an event you should send the same payload, but for unsubscribe
.
caution
If you subscribe to an event published with both projectId
and layoutId
, a subscription against projectId
will receive all events. If the event only contains layoutId
, the subscription will not (even though they are related logically!)
The following namespaces are utilized:
apistream:live:*
- Events originating from the Live API. You can find the underpinning protobuf here.apistream:layout:*
- Events originating from the Layout API. You can find the underpinning protobuf here.
tip
When subscribing to an event you can choose to subscribe to a single event or multiple events via the use of a wildcard. e.g to receive a specific Layout API event you could subscribe to apistream:layout:EVENT_TYPE_LAYOUT:EVENT_SUB_TYPE_UPDATE
while you could subscribe to apisteam:layout:EVENT_TYPE_LAYOUT:*
to receive all updates against a layout.
Example payload:
{
"subscribe": {
"name": "apistream:layout:EVENT_TYPE_LAYOUT:*",
"target": {
"projectId": "MY_PROJECT_ID"
}
},
"correlationId": "9436c822-b4cf-41a2-be7d-9946e5570c48"
}
Successful response:
{
"result":{
"correlationId": "9436c822-b4cf-41a2-be7d-9946e5570c48",
"sessionId": "04a2f8dd-930f-41ee-8887-fd4351990637",
"subscribed": {
"name": "apistream:layout:EVENT_TYPE_LAYOUT:*",
"target": {
"projectId": "MY_PROJECT_ID"
}
}
}
}
Receiving events
When an event is distributed that matches a subscription, the event will be forwarded along. We suggest de-duplicating by id
to ensure you don't receive the event twice.
{
"result": {
"sessionId": "09faf271-85d5-40d7-be7a-0f5488de8c43",
"event": {
"name": "apistream:live:EVENT_TYPE_PROJECT:EVENT_SUB_TYPE_UPDATE",
"payload": {
/* payload */
},
"target": {
"collectionId": "623926b4eb0fddc33dfe50d7",
"projectId": "6239275feb0fddc33dfe5100"
},
"id": "60f545b5-3636-4274-9b83-ee842dc13438"
}
}
}
Publishing events
The events API provides a powerful mechanism enabling you to use the events API as message bus. This is intended to provide you a communication channel between your backend and the layout compositor.
caution
Events prefixed with apistream:
cannot be published to, these are reserved for internal use only.
A publish payload:
{
"publish": {
"name": "my_custom_event",
"payload": {
"chat": {
"user": "api.stream",
"message": "Hello!"
}
},
"target": {
"projectId": "my_project_id"
}
}
}