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
For backend integrations (CRM sync, analytics, workflow automation), see Receiving Webhooks for HTTP POST delivery with signature verification.
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:
- Your client opens a persistent HTTP connection to
/v1/events - DialStack pushes events to your client as they occur
- 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
Using the DialStack SDK (Recommended)
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():
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:
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.
{}
call.incoming
Sent when an incoming call arrives for the account. This is the primary event for implementing screen pops.
{
"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"
}
| Field | Type | Description |
|---|---|---|
event | string | Always "call.incoming" |
account_id | string | Account receiving the call |
call_id | string | Call identifier |
from_number | string | Caller's phone number (E.164 format) |
from_name | string | null | Caller's name from caller ID (if available) |
to_number | string | Called phone number (E.164 format) |
user_id | string | User 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 |
timestamp | string | When the event was sent |
call.initiated
Sent when an outbound call starts dialing.
{
"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.
{
"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.
{
"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.
{
"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)
Related Resources
- API Reference - Complete API documentation
- Authentication - Authentication guide
- Sessions - Creating account sessions