Skip to main content

Events & Real-time

Almost everything that happens in Cortex emits a domain event: a session starts, a message is transcribed, a participant joins, a sanction is created, an invitation is accepted. These events are the backbone of real-time integrations — and they’re all drawn from a single catalog.

One catalog, three delivery surfaces

You choose how to receive events based on what you’re building:

Webhooks

Durable, retried, signed delivery to your backend. Best for server-to-server.

Webhooks
Server-Sent Events

A live stream for dashboards and UI. The SDK’s watch() is built on this.

Functions

Run your own code on each event with named on<Event> handlers.

The event envelope

Every event shares the same envelope:

interface DomainEvent<T> {
id: string; // unique id — dedupe on this
type: string; // e.g. "message.created"
source: string; // which service emitted it
tenantId: string;
projectId?: string;
resourceId: string; // the affected resource, e.g. "session:abc-123"
timestamp: string; // ISO-8601
payload: {
action: "created" | "updated" | "deleted" | "occurred";
data: T; // the resource state
changes?: Partial<T>; // on "updated": the previous values of changed fields
};
}

The payload is action-typed. On an updated event, changes carries the old values of the fields that changed — so you can tell what actually changed without diffing against your own copy.

See the Domain Events reference for the full list of event types and their payloads.

Server-Sent Events (SSE)

For live UI, open an SSE stream and receive events as they happen. The simplest way is the SDK’s watch(), which loads the current state over REST and then keeps it in sync over SSE — no manual EventSource, filtering or merge logic:

const sub = project.sessions.watch();

sub.onSnapshot((sessions, changes) => {
for (const change of changes) {
console.log(change.type, change.id); // "added" | "modified" | "removed"
}
});

sub.onError((err) => console.error(err));
// later: sub.unsubscribe();

watch() works for collections and single objects, and resilient reconnection is handled for you — every reconnect re-syncs state so you’re never left stale. See the Real-time updates guide.

SSE is live, not replayed

Server-Sent Events are for now. If you need durable, replayable delivery (so nothing is missed while your service is offline), use webhooks or functions instead.

Subscribing from functions

In a serverless function, subscribe to an event by exporting a handler named after it (message.createdonMessageCreated). The subscription is derived from your exports — there’s no list to maintain.

exports.onMessageCreated = async (event, ctx) => {
// event.payload.data is the new message
};

Subscribing from webhooks

Pass the event names (or wildcards) when you create a subscription:

await project.webhooks.createSubscription({
url: "https://example.com/hook",
events: ["session.*", "message.created", "gathering.invitation.sent"],
secret: webhookSecret,
});