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 Webhook Events for HTTP POST delivery with signature verification.
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
SSE is supported in all modern browsers and has built-in reconnection handling.
Connecting to the Event Stream
Using the DialStack SDK (Recommended)
The simplest way to receive events is using the DialStack SDK, which handles authentication and reconnection automatically:
import { loadDialStack } from '@dialstack/sdk';
// Initialize the SDK
const dialstack = await loadDialStack('pk_test_...', {
fetchClientSecret: async () => {
const response = await fetch('/api/dialstack/session');
return response.json();
},
});
// Subscribe to call events
dialstack.on('call.incoming', (event) => {
console.log('Incoming call from:', event.from_number);
showScreenPop(event);
});
dialstack.on('call.answered', (event) => {
console.log('Call answered:', event.call_id);
});
dialstack.on('call.end', (event) => {
console.log('Call ended:', event.call_id, event.status);
});
// Later, unsubscribe when done
dialstack.off('call.incoming');
The SDK uses fetch with proper Authorization headers internally and handles reconnection with exponential backoff.
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
}
}
}
}
Event Types
connected
Sent immediately when the connection is established. Use this to confirm the stream is working.
{
"message": "Connected to event stream"
}
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",
"from_number": "+14155551234",
"from_name": "John Smith",
"to_number": "+14155559876"
}
| Field | Type | Description |
|---|---|---|
event | string | Always "call.incoming" |
account_id | string | Account receiving the call |
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) |
call.initiated
Sent when an outbound call starts dialing.
{
"event": "call.initiated",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"from_number": "+14155559876",
"to_number": "+14155551234",
"user_id": "user_01h2xcejqtf2nbrexx3vqjhp42"
}
call.answered
Sent when a call is answered.
{
"event": "call.answered",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"from_number": "+14155551234",
"to_number": "+14155559876",
"direction": "inbound",
"answered_at": "2026-01-15T14:30:05Z"
}
call.end
Sent when a call ends. The status field indicates the outcome.
{
"event": "call.end",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"from_number": "+14155551234",
"to_number": "+14155559876",
"direction": "inbound",
"status": "completed",
"duration_seconds": 325,
"ended_at": "2026-01-15T14:35:30Z"
}
call.transfer
Sent when a call is transferred.
{
"event": "call.transfer",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"from_number": "+14155551234",
"to_number": "+14155559876",
"transferred_to": "1001"
}
Screen Pop Example
Here's a complete example of implementing a screen pop when calls arrive using the SDK:
import { loadDialStack } from '@dialstack/sdk';
class ScreenPopManager {
constructor() {
this.dialstack = null;
}
async connect() {
// Initialize the SDK
this.dialstack = await loadDialStack('pk_test_...', {
fetchClientSecret: async () => {
const response = await fetch('/api/dialstack/session');
return response.json();
},
});
// Subscribe to incoming calls
this.dialstack.on('call.incoming', (call) => {
this.handleIncomingCall(call);
});
}
async handleIncomingCall(call) {
// Look up customer by phone number
const customer = await this.lookupCustomer(call.from_number);
// Show the screen pop
this.showPopup({
title: call.from_name || 'Unknown Caller',
phone: call.from_number,
customer: customer,
});
}
async lookupCustomer(phoneNumber) {
// Query your database for customer info
const response = await fetch(`/api/customers?phone=${phoneNumber}`);
return response.json();
}
showPopup(data) {
// Display the screen pop UI
const popup = document.createElement('div');
popup.className = 'screen-pop';
popup.innerHTML = `
<h2>Incoming Call</h2>
<p><strong>${data.title}</strong></p>
<p>${data.phone}</p>
${data.customer ? `<p>Customer: ${data.customer.name}</p>` : ''}
`;
document.body.appendChild(popup);
}
disconnect() {
if (this.dialstack) {
this.dialstack.logout();
this.dialstack = null;
}
}
}
// Usage
const screenPop = new ScreenPopManager();
screenPop.connect();
Connection Management
Automatic Reconnection
The DialStack SDK automatically handles reconnection with exponential backoff when the connection drops. No additional configuration is needed.
Session Expiry
Account sessions expire after 1 hour by default. The SDK's fetchClientSecret callback is called to refresh the session automatically. Ensure your callback fetches a fresh session from your server:
const dialstack = await loadDialStack('pk_test_...', {
fetchClientSecret: async () => {
// This is called on init and when the session needs refreshing
const response = await fetch('/api/dialstack/session');
const { client_secret, expires_at } = await response.json();
return { clientSecret: client_secret, expiresAt: expires_at };
},
});
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
- 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 after 1 hour)
Related Resources
- API Reference - Complete API documentation
- Authentication - Authentication guide
- Sessions - Creating account sessions