Skip to main content

Webhook Events

Receive real-time notifications about calls, recordings, voicemails, and transcriptions via HTTP webhooks.

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

Overview

Webhook events are push notifications that DialStack sends to your server as things happen in the system. Unlike appointment webhooks (which are request/response), event webhooks are fire-and-forget notifications — return 200 to acknowledge receipt.

Webhook events are platform-scoped: configure a single webhook URL on your platform, and DialStack delivers events for all accounts on that platform.

For frontend real-time notifications (account-scoped), see Real-Time Events (SSE).

Configuration

To receive webhook events, configure a webhook_url on your platform via the platform management API. DialStack automatically generates and manages the webhook_secret used for signature verification.

All events for all accounts on the platform are delivered to this single URL.

Event Types

EventDescription
call.initiatedOutbound call started
call.incomingInbound call received
call.ringingA user's phones are about to ring
call.answeredCall answered
call.endCall ended
call.transferCall transferred
call.emergencyAn emergency number was dialed (911 or regional equivalent)
queue.call.queuedCaller entered a call queue
queue.call.dispatchedQueue dispatched the caller to agent targets
queue.call.answeredQueue caller was answered by an agent
queue.call.abandonedCaller hung up while waiting in queue
queue.call.timed_outQueue wait timed out before an agent answered
queue.call.completedAnswered queue call completed
queue.call.callback_requestedCaller requested a queue callback
queue.call.callback_attemptedQueue callback attempt completed
queue.call.callback_failedQueue callback request reached a terminal failure
voicemail.newNew voicemail received
recording.availableCall recording ready for download
recording.transcription.completeCall transcript ready
recording.summary.completeAI call summary ready
voicemail.transcription.completeVoicemail transcript ready

Event Envelope

All webhook events share this envelope structure:

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1v2",
"type": "call.end",
"created_at": "2026-01-15T14:35:30Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {}
}
FieldTypeDescription
idstringUnique event identifier. Use for idempotent processing.
typestringEvent type (see table above).
created_atstringISO 8601 timestamp when the event was created.
account_idstringAccount where the event occurred.
dataobjectEvent-specific payload (see below).

Event Payloads

call.initiated

Sent when an outbound call starts dialing.

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1v2",
"type": "call.initiated",
"created_at": "2026-01-15T14:30:00Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"direction": "outbound",
"from_number": "+14155559876",
"to_number": "+14155551234",
"user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"from_user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"started_at": "2026-01-15T14:30:00Z"
}
}

call.incoming

Sent when an inbound call is received.

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1v3",
"type": "call.incoming",
"created_at": "2026-01-15T14:30:00Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"direction": "inbound",
"from_number": "+14155551234",
"from_name": "John Smith",
"to_number": "+14155559876",
"user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"to_user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"started_at": "2026-01-15T14:30:00Z"
}
}
FieldTypeDescription
call_idstringCall identifier
directionstringAlways "inbound"
from_numberstringCaller's phone number (E.164)
from_namestring | nullCaller's display name
to_numberstringCalled phone number (E.164)
user_idstring | nullUser the call is routed to. Only set when the called number targets a single user directly; omitted when the call routes to a ring group, voice app, dial plan, or any other destination where a specific user is not known at this stage.
from_user_idstring | nullCalling user (DIA-801). See User attribution below.
to_user_idstring | nullCalled user (DIA-801). See User attribution below.
started_atstringISO 8601 timestamp

call.ringing

Sent when a user's phones are about to ring. Fired once per (call, user) — a user with multiple phones (desk + mobile) produces a single event, not one per device. For a ring group or ring-all-users call, one event is emitted per user being rung, all sharing the same call_id. user_id always identifies the user whose phones are ringing, never the caller.

Not emitted for the initiating user's own phone ringing during a click-to-call (leg A).

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1v8",
"type": "call.ringing",
"created_at": "2026-01-15T14:30:01Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"from_number": "+14155551234",
"from_name": "John Smith",
"to_number": "+14155559876",
"ringing_at": "2026-01-15T14:30:01Z"
}
}
FieldTypeDescription
call_idstringCall identifier
user_idstringUser whose phones are about to ring
from_numberstringCaller's phone number (E.164)
from_namestring | nullCaller's display name
to_numberstringCalled phone number (E.164)
ringing_atstringISO 8601 timestamp when the ring began

call.answered

Sent when a call is answered.

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1v4",
"type": "call.answered",
"created_at": "2026-01-15T14:30:05Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"direction": "inbound",
"from_number": "+14155551234",
"from_name": "John Smith",
"to_number": "+14155559876",
"user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"to_user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"started_at": "2026-01-15T14:30:00Z",
"answered_at": "2026-01-15T14:30:05Z"
}
}

call.end

Sent when a call ends.

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1v5",
"type": "call.end",
"created_at": "2026-01-15T14:35:30Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"direction": "inbound",
"from_number": "+14155551234",
"from_name": "John Smith",
"to_number": "+14155559876",
"user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"to_user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"started_at": "2026-01-15T14:30:00Z",
"answered_at": "2026-01-15T14:30:05Z",
"ended_at": "2026-01-15T14:35:30Z",
"duration_seconds": 325,
"status": "completed"
}
}
status valueDescription
completedCall was answered and ended normally
no-answerCall rang but was not answered
busyCallee was busy or rejected the call
failedCall failed due to a network or system error
voicemailCall went to voicemail
direction valueDescription
inboundCall from PSTN to a DID
outboundCall from an endpoint to PSTN
internalExtension-to-extension or feature code

user_id is populated with the user most closely associated with the call (the caller for outbound, the answerer for inbound and internal) and is omitted when no single user was involved — for example, an unanswered inbound ring group call or a call handled entirely by a voice app.

The generic call.* events are queue-agnostic; queue context is exposed via the dedicated queue.call.* events below. Subscribe to both streams and correlate by call_id when you need queue analytics alongside generic call state.

Queue call lifecycle

Queue lifecycle events are emitted in addition to the generic call.* lifecycle. Use them when you need queue-specific analytics or routing decisions without inferring queue state from call.end.

Common fields:

FieldTypeDescription
call_idstringCall identifier
queue_idstringQueue identifier
queue_namestringQueue display name
from_numberstringCaller's phone number (E.164)
from_namestring | nullCaller's display name
to_numberstringCalled phone number (E.164)
position_at_admitnumberCaller position when admitted to the queue, omitted when unavailable
wait_secondsnumberSeconds spent waiting in queue before this outcome

Event-specific fields:

EventAdditional fields
queue.call.queuedqueued_at
queue.call.dispatchedagents_claimed, targets_dispatched, dispatched_at
queue.call.answeredagent_user_id, agent_endpoint_id, answered_at
queue.call.abandonedabandoned_at
queue.call.timed_outtimed_out_at
queue.call.completedagent_user_id, agent_endpoint_id, completed_at
queue.call.callback_requestedcallback_id, original_priority, original_entered_at, requested_at
queue.call.callback_attemptedcallback_id, attempt_number, result, next_attempt_at
queue.call.callback_failedcallback_id, attempts, reason

User attribution

call.initiated, call.incoming, call.answered, and call.end also carry from_user_id and to_user_id so both sides of a call are visible — most usefully on internal user-to-user calls, where user_id alone only identifies the answerer. Both fields are omitted when the side in question is not a user on this account (for example, an external PSTN caller has no from_user_id).

Call typefrom_user_idto_user_id
Inbound PSTN → useromitted (external caller)The routed user
Inbound PSTN → ring group, answeredomittedThe answering member
Inbound PSTN → voice app or dial planomittedomitted (until a user is reached)
Inbound PSTN → user with Find Me / Follow MeomittedThe Find Me / Follow Me owner
Outbound from an endpointThe endpoint's useromitted (external destination)
Internal user-to-userThe calling userThe called user
Click-to-callThe initiating userThe answering endpoint's user, when applicable

user_id is preserved with its original semantics as a convenience alias for existing integrations; new integrations should prefer from_user_id and to_user_id.

call.transfer

Sent when a call is transferred.

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1v6",
"type": "call.transfer",
"created_at": "2026-01-15T14:32:00Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"direction": "inbound",
"from_number": "+14155551234",
"to_number": "+14155559876",
"transferred_to": "1001",
"transferred_by": "user_01h2xcejqtf2nbrexx3vqjhp42",
"transferred_at": "2026-01-15T14:32:00Z"
}
}

call.emergency

Sent when an emergency number is dialed from any endpoint on the account. Subscribers can route this event to a paging system, security desk, or any other on-call destination.

The event also fires for carrier emergency test numbers (such as 933 in the US) so you can verify your wiring end-to-end without contacting an actual public safety answering point.

e911_provisioned is false when the dialing endpoint had no provisioned dispatchable location at the time of the call. In that case location_id and location_name are omitted from the payload. Treat that combination as a signal that an emergency call went out without an accurate address — the account holder should provision the location before the next call.

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1v8",
"type": "call.emergency",
"created_at": "2026-05-03T18:30:00Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"to_number": "911",
"user_id": "user_01h2xcejqtf2nbrexx3vqjhp47",
"device_id": "dev_01h2xcejqtf2nbrexx3vqjhp49",
"extension": "1001",
"location_id": "loc_01h2xcejqtf2nbrexx3vqjhp48",
"location_name": "Headquarters",
"location_address": "123 Main St, Springfield, IL 62701, US",
"e911_provisioned": true,
"placed_at": "2026-05-03T18:30:00Z"
}
}

voicemail.new

Sent when a new voicemail is received. Use GET /v1/voicemails/{voicemail_id} to retrieve the full voicemail details and audio.

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1v7",
"type": "voicemail.new",
"created_at": "2026-01-15T14:36:00Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"voicemail_id": "vm_01h2xcejqtf2nbrexx3vqjhp46",
"user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"from_number": "+14155551234",
"from_name": "John Smith",
"duration_seconds": 42,
"created_at": "2026-01-15T14:36:00Z"
}
}

user_id is included when the voicemail belongs to a single user's mailbox and is omitted when it belongs to a shared voicemail box.

recording.available

Sent when a call recording is ready for download. Use GET /v1/calls/{call_id}/recording to download the audio file.

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1v8",
"type": "recording.available",
"created_at": "2026-01-15T14:35:35Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"duration_seconds": 325
}
}

recording.transcription.complete

Sent when a call transcription is ready. Use GET /v1/calls/{call_id}/transcript to retrieve the full transcript text.

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1v9",
"type": "recording.transcription.complete",
"created_at": "2026-01-15T14:35:45Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45",
"status": "completed"
}
}

recording.summary.complete

Sent when an AI-generated call summary is ready. Use GET /v1/calls/{call_id}/transcript to retrieve the summary.

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1va",
"type": "recording.summary.complete",
"created_at": "2026-01-15T14:36:00Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"call_id": "call_01h2xcejqtf2nbrexx3vqjhp45"
}
}

voicemail.transcription.complete

Sent when a voicemail transcription is ready. Use GET /v1/voicemails/{voicemail_id}/transcript to retrieve the transcript text.

{
"id": "evt_01jqr5k8m3n4p6q7r8s9t0u1vb",
"type": "voicemail.transcription.complete",
"created_at": "2026-01-15T14:36:10Z",
"account_id": "acct_01h2xcejqtf2nbrexx3vqjhp41",
"data": {
"voicemail_id": "vm_01h2xcejqtf2nbrexx3vqjhp46",
"user_id": "user_01h2xcejqtf2nbrexx3vqjhp42",
"status": "completed"
}
}

Webhook Protocol

Request Format

DialStack sends a POST request to your platform's configured webhook URL with the event as the JSON body.

Headers:

HeaderDescription
Content-Typeapplication/json
X-DialStack-SignatureHMAC-SHA256 signature for verification
X-DialStack-Account-IdAccount where the event occurred
User-AgentDialStack-Webhook/1.0

Signature Verification

Every webhook request is signed using HMAC-SHA256 with your platform's webhook secret. The signature header format is:

X-DialStack-Signature: t=1705312530,v1=a1b2c3d4e5f6...

To verify:

  1. Extract the timestamp (t) and signature (v1) from the header
  2. Construct the signed payload: {timestamp}.{request_body}
  3. Compute HMAC-SHA256 of the signed payload using your webhook secret
  4. Compare with the provided signature

For detailed verification examples in Node.js, Python, and Go, see Appointment Webhooks — Signature Verification.

Expected Response

Return any 2xx status code to acknowledge receipt. The response body is ignored. Non-2xx responses trigger a retry.

Retry Behavior

Failed deliveries (non-2xx response or timeout) are retried automatically:

  • Up to 5 retry attempts at approximately 60-second intervals
  • 30-second timeout per delivery attempt
  • After all retries are exhausted, the event is moved to a dead letter queue for investigation

Ordering

Events for the same account are delivered in order. Events for different accounts may be delivered in parallel. Do not assume ordering across accounts.

Timing

EventTypical Latency
call.initiated< 1 second
call.incoming< 1 second
call.ringing< 1 second
call.answered< 1 second
call.end< 1 second
call.transfer< 1 second
queue.call.*< 1 second
voicemail.new< 2 seconds
recording.available< 5 seconds after call ends
recording.transcription.complete~10 seconds after recording
recording.summary.complete~15 seconds after recording
voicemail.transcription.complete~10 seconds after voicemail

Best Practices

  • Verify signatures on every request to prevent spoofing
  • Return 200 quickly and defer processing — long-running handlers risk timeouts
  • Handle events idempotently using the id field — events may be delivered more than once
  • Don't rely on strict cross-account ordering — events from different accounts may arrive in any order