Events
Every change, as an event.
Events arrive at your webhooks and stay in your application's event log, which you can page at any time to recover a gap or to poll instead of receiving.
Fields every event carries
| Field | Meaning |
|---|---|
id | Unique per event; dedupe by it (delivery is at least once). |
type | The event type, below. |
occurredAt | When the change was written (ISO 8601). It can precede the commit: order by sequence. |
applicationId | Your application. |
workspaceId | SiteCall's workspace id. |
externalWorkspaceId | Your own id for that workspace (null only for a number in SiteCall's inventory). |
sequence | Increases within the event's stream, in commit order. Gaps are normal. |
callId | Call, queue, recording, transcript and voicemail events. |
externalUserId | Events about one of your users, by your own id. |
Types
| Type | Stream | When |
|---|---|---|
call.started | call | A call reached the engine (inbound) or was placed (outbound). |
call.ringing | call | One ring of people's devices: members and application users rung, the offer, its expiry. |
call.answered | call | The caller's leg was answered; call.state says by what (a person, the AI, a menu, voicemail). |
call.connected | call | A party took the call (a device, the AI, a forwarded number, an outbound destination); leg names it. Again after each answered transfer. |
call.transferred | call | A blind transfer, or a warm one at completion: target, warm, actor. |
call.ended | call | The call ended: the state it ended from, whether a person or AI connected, duration. |
queue.entered | call | The call entered a queue (waiting, offered or a callback). |
queue.left | call | The call left a queue: connected, abandoned or overflowed. |
agent.state_changed | agent | An agent's state changed (available, offered, on_call, wrap_up, away, offline). |
recording.ready | call | A call recording or voicemail is stored; fetch a link with the recordings API. |
transcript.ready | call | A recording's transcript: text, per-party segments (bounded), channels. |
voicemail.ready | call | A caller left a voicemail. |
message.received | number | An SMS or MMS arrived on a workspace number. |
message.status | number | An outbound message's status changed (sent, delivered, failed). |
number.updated | number | A number's status, lanes, texting registration or workspace changed. |
Fields are camelCase and only ever added. A state-like field (a call's state or direction, a queue entry's state, an agent's state, a recording's kind, a number's status) may take a value not listed today, and new event types may appear: ignore fields you do not know, treat an unknown value as "other", and acknowledge an unknown type. The client's verifyWebhook and event log return such an event as { type: "unrecognized" } instead of failing.
Streams and order
Each event belongs to one stream: call:<callId>, number:<numberId> or agent:<memberId>. Within a stream, sequence follows commit order, which is the order webhook deliveries take. Across streams there is no order: two calls' events interleave freely.
The event log
GET /api/v1/events (an application key, scope webhooks:*) pages your events in log order, the order their transactions committed. A position it handed out is never overtaken: an event that commits later is always served after it, so a poller that follows nextCursor misses nothing. The log serves only committed transactions older than every one still running, so it can lag behind a slow transaction, but it never skips.
| Query | Meaning |
|---|---|
after | A nextCursor (resume exactly after it), or an event id you received by webhook (resume from where it committed: some events around it come again, so dedupe by id). Absent: from the start of the log. |
limit | 1–500, default 50. |
type | Comma-separated event types; default all. |
The answer is { data, nextCursor, hasMore }. hasMore says more events are listed already: ask again at once with after=nextCursor. When it is false, keep nextCursor and ask again later to poll. Within a page one stream's events may interleave with others', so order a stream by sequence. A workspace header narrows the log to that workspace. A position the log never handed out is a 400 invalid_cursor.
gap recovery
import { createSiteCallClient } from "@sitecall/cli";
const sitecall = createSiteCallClient({ apiKey: process.env.SITECALL_API_KEY });
let after = await loadCursor(); // a nextCursor you stored, or the last event id you processed
for await (const page of sitecall.events.pages({ after, type: ["call.ended", "transcript.ready"] })) {
for (const event of page.data) {
if (event.type !== "unrecognized" && !(await alreadyHandled(event.id))) {
await handle(event);
}
}
if (page.nextCursor) await saveCursor(page.nextCursor);
}GET /api/v1/events
curl "https://sitecall.ai/api/v1/events?after=c1.20314.7&limit=100" \
-H "authorization: Bearer $SITECALL_API_KEY"
{ "data": [ { "id": "…", "type": "call.ended", "sequence": 6, … } ],
"nextCursor": "c1.20322.1",
"hasMore": false }