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 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:

  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

SSE is supported in all modern browsers and has built-in reconnection handling.

Connecting to the Event Stream

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"
}
FieldTypeDescription
eventstringAlways "call.incoming"
account_idstringAccount receiving the call
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)

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)