Skip to main content

Real-Time Events

DialStack provides real-time event streaming via Server-Sent Events (SSE), enabling your application to receive instant notifications about incoming calls and other account activity.

Real-Time Events is the browser transport for call events. For the backend transport, see Webhook Events. For common patterns built on top, see Screen Pop and Activity Logging.

Overview​

Real-time events allow you to:

  • Show screen pops when calls arrive (caller ID, customer info)
  • Update UI instantly without polling the API
  • Build responsive applications that react to call activity in real-time
Looking for server-side webhooks?

For backend integrations (CRM sync, analytics, workflow automation), see Receiving Webhooks for HTTP POST delivery with signature verification.

The webhook transport uses a different shape

The same call events are also delivered to server-side Webhook Events, but in a different shape: each webhook is wrapped in an envelope ({ id, type, created_at, account, data }) and uses type where the SSE payloads shown here use event. SSE events carry no id, so they cannot be deduplicated by event ID the way webhooks can. Treat the two transports as separate schemas rather than assuming identical field names.

How It Works​

DialStack uses Server-Sent Events (SSE), a standard web technology for streaming events from server to client:

  1. Your client opens a persistent HTTP connection to /v1/events
  2. DialStack pushes events to your client as they occur
  3. The connection stays open until your client disconnects

The stream is scoped to one account: it is authenticated with a session token for that account.

Connecting to the Event Stream​

The SDK opens the stream for you, authenticates it, refreshes the session and reconnects with exponential backoff. It exposes the call.incoming event through on():

JavaScript
import { loadDialstackAndInitialize } from '@dialstack/sdk-js';

const dialstack = await loadDialstackAndInitialize({
publishableKey: 'pk_test_...',
fetchClientSecret: async () => {
const response = await fetch('/api/dialstack/session');
const { client_secret, expires_at } = await response.json();
return { clientSecret: client_secret, expiresAt: expires_at };
},
});

function onIncomingCall(event) {
console.log('Incoming call from:', event.from_number);
showScreenPop(event);
}

dialstack.on('call.incoming', onIncomingCall);

// Later, unsubscribe when done
dialstack.off('call.incoming', onIncomingCall);

The SDK's call.incoming event carries from_number, from_name, to_number, user_id (only when the call routes to a single user directly) and timestamp (a Date). To receive the other event types below, read the stream directly.

Using fetch with ReadableStream​

For direct API access without the SDK, use fetch with a readable stream:

JavaScript
async function connectToEvents(clientSecret) {
const response = await fetch('https://api.dialstack.ai/v1/events', {
headers: {
Authorization: `Bearer ${clientSecret}`,
Accept: 'text/event-stream',
},
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
const { value, done } = await reader.read();
if (done) break;

const text = decoder.decode(value);
const lines = text.split('\n');

for (const line of lines) {
if (line.startsWith('event: ')) {
const eventType = line.slice(7);
// Handle event type
} else if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
// Handle event data
}
}
}
}

When you read the stream yourself, reconnect when it drops, and fetch a fresh session token when the server answers 401.

Event Types​

Every payload is flat and names its type in event. Every payload also carries timestamp, the time the event was sent (ISO 8601).

connected​

Sent immediately when the connection is established, with an empty object as its data. Use this to confirm the stream is working.

JSON
{}

call.incoming​

Sent when an incoming call arrives for the account. This is the primary event for implementing screen pops.

JSON
{
"event": "call.incoming",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"from_number": "+14155551234",
"from_name": "John Smith",
"to_number": "+14155559876",
"user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"timestamp": "2026-01-15T14:30:00Z"
}
FieldTypeDescription
eventstringAlways "call.incoming"
account_idstringAccount receiving the call
call_idstringCall identifier
from_numberstringCaller's phone number (E.164 format)
from_namestring | nullCaller's name from caller ID (if available)
to_numberstringCalled phone number (E.164 format)
user_idstringUser the call is routed to. Only present when the called number targets a single user directly, not a ring group, voice app or dial plan
timestampstringWhen the event was sent

call.initiated​

Sent when an outbound call starts dialing.

JSON
{
"event": "call.initiated",
"account": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"from_number": "+14155559876",
"to_number": "+14155551234",
"user": "user_01h2xcejqtf2nbrexx3vqjhp42",
"timestamp": "2026-01-15T14:30:00Z"
}

user is the user who placed the call. account and user are also sent under the deprecated account_id and user_id keys.

call.answered​

Sent when a call is answered.

JSON
{
"event": "call.answered",
"account": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"from_number": "+14155551234",
"to_number": "+14155559876",
"direction": "inbound",
"answered_at": "2026-01-15T14:30:05Z",
"connected_at": "2026-01-15T14:30:47Z",
"related_call": null,
"timestamp": "2026-01-15T14:30:05Z"
}

answered_at and connected_at mean the same as on the call.answered webhook. related_call names another call in the same conversation, such as the parked call a retrieve resumed, and is null otherwise.

call.end​

Sent when a call ends. The status field indicates the outcome. duration_seconds runs from answer to hangup, and it is 0 for a call that was never answered.

JSON
{
"event": "call.end",
"account": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"from_number": "+14155551234",
"to_number": "+14155559876",
"direction": "inbound",
"status": "completed",
"duration_seconds": 325,
"connected_at": "2026-01-15T14:30:47Z",
"ended_at": "2026-01-15T14:35:30Z",
"related_call": null,
"timestamp": "2026-01-15T14:35:30Z"
}

status takes the same values as on the call.end webhook.

call.transfer​

Sent when a call is transferred.

JSON
{
"event": "call.transfer",
"account": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"from_number": "+14155551234",
"to_number": "+14155559876",
"transferred_to": "1001",
"related_call": "call_01h2xcejqtf2nbrexx3vqjhp47",
"timestamp": "2026-01-15T14:32:00Z"
}

related_call is the other call in the transfer pair, as on the call.transfer webhook.

On call.answered, call.end and call.transfer, account is also sent under the deprecated account_id key.

Other event types, such as call.ringing, are delivered only as webhooks.

Connection Management​

The DialStack SDK reconnects with exponential backoff when the connection drops, and calls your fetchClientSecret again when the session needs refreshing. See Session tokens for session lifetime and refresh.

Best Practices​

DO​

  • Use the DialStack SDK for automatic reconnection and session management
  • Subscribe to events when your application loads
  • Look up additional customer data when calls arrive for richer screen pops — see Screen Pop
  • Call dialstack.logout() when the user logs out or navigates away

DON'T​

  • Don't poll the API instead of using events (inefficient and delayed)
  • Don't create multiple SDK instances for the same account
  • Don't store the client secret long-term (it expires)