Call Webhooks #
Use the Settings portal to register HTTPS endpoints that receive call events from your Dataphone environment.
Overview #
Call webhooks let external systems react to phone activity in real time. When a webhook is enabled, Dataphone sends HTTP POST requests with JSON payloads to your configured URL for the events and call directions you selected.
Management happens in the portal under Settings → PBX Management → Call Webhooks (/settings/groups-management/call-webhooks).
Create and configure the webhook
↓
Send test deliveries with real payloads to validate your integration
↓
Contact Dataphone support to go live
↓
Dataphone POSTs call events to your endpoint
New webhooks are created disabled. They do not deliver live call events until Dataphone support enables them for your account.
Permissions #
You need access to the Call Webhooks page in Settings. If you do not see PBX Management → Call Webhooks, ask your account administrator to grant webhook access.
As an admin, you can typically:
- View webhooks and delivery history
- Create and edit webhook configuration
- Send test deliveries with real call event payloads
Going live (enabling a webhook for real call events) is handled by Dataphone support, not from this page.
Call Webhooks page #
The main page lists all webhooks for the current account. From here you can:
- Create a new webhook subscription.
- Search existing webhooks by label, URL, description, auth type, events, and directions.
- Review each webhook’s destination, subscribed events, call directions, auth method, retry mode, and enabled/disabled status.
- View recent delivery attempts and inspect payloads, responses, and errors.
- Send test deliveries before the webhook is live.
- Edit webhook configuration.
Enabled/disabled status is read-only for admins. Contact Dataphone support when a webhook is ready to go live.
Create or edit a webhook #
Open Add New Webhook or click the edit action on an existing row.
Fields #
| Field | Required | Notes |
|---|---|---|
| Label | Yes | |
| Destination URL | Yes | Must be a valid HTTPS URL |
| Description | No | |
| Retry mode | Yes | Key call events, all events, or never retry |
| Events | Yes | One or more call events |
| Directions | Yes | Inbound, outbound, and/or local |
| Auth method | Yes | HMAC signature, bearer token, basic auth, or none |
| HMAC secret | When creating with HMAC, or changing auth to HMAC | At least 16 characters |
| Bearer token | When creating with bearer, or changing auth to bearer | |
| Username / password | When creating with basic, or changing auth to basic | |
| Custom headers JSON | No | JSON object merged into outbound delivery headers |
| Max attempts | No | Override default retry attempt count (1–5) |
| Backoff seconds | No | Comma-separated retry delays in seconds |
Editing credentials #
When editing a webhook without changing the auth method, leave credential fields blank to keep the current secret or token. If you change the auth method, enter new credentials (except when switching to None).
Going live #
The create/edit form does not include an enabled switch. New webhooks start disabled and stay that way until Dataphone support enables them.
After you have tested successfully, contact Dataphone support with the webhook label and ask them to enable it for live delivery.
Send test deliveries #
Use Send test delivery to exercise your integration before going live. Each test sends real JSON payloads—the same shape as live call events—to your endpoint so you can verify delivery, auth, headers, and how your system handles the event data.
Tests work on disabled webhooks and do not require the webhook to be enabled.
The test modal sends one or more events in sequence. You can customize caller, destination, timestamps, and other fields to match the scenarios you care about.
Test form fields #
| Field | Description |
|---|---|
| Events to test | Subset of the webhook’s subscribed events |
| Interval between events | Delay in seconds between events in the sequence (0–60) |
| Direction | Inbound, outbound, or local |
| Call UUID | Optional. Auto-generated if left blank |
| Caller / destination | Number and name used in the sample payload |
| Test party / answered by | Party fields for ringing, answered, and related events |
| Timestamps and durations | Optional values for answered, completed, and missed scenarios |
| Ended reason / ringing ended reason | Optional outcome fields |
| Parking extension | Used for parked/unparked test events |
After sending, use View deliveries to confirm the HTTP status, response body, and payload JSON.
View delivery history #
View deliveries lists recent deliveries for that webhook.
Each row shows:
- Event type (click to open the payload)
- Status (
pending,delivered,failed) - HTTP response status
- Attempt number
- Created time
- Error message, if any
Click an event or row to inspect:
- Delivery metadata (call UUID, attempt, delivered time, next retry)
- Response body from your server
- Full payload JSON, with a copy button
Use this view to debug failed deliveries and confirm test payloads before asking Dataphone support to go live. Delivery history is retained for about 1 day because it is intended for short-term debugging.
Subscribable events #
When selecting events in the create/edit form, each option shows the event name (for example call.started) and a short description.
| Event | Description | Retried by default |
|---|---|---|
call.started |
A new call is first visible | Yes |
call.ringing |
A party started ringing | No |
call.ringing_stopped |
A previously ringing party stopped ringing | No |
call.answered |
A party answered the call | Yes |
call.missed |
The call ended without any party answering | Yes |
call.completed |
An answered call ended | Yes |
call.transferred |
The call destination or flow changed | No |
call.held |
A participant placed the call on hold | No |
call.resumed |
A participant resumed a held call | No |
call.parked |
The call was parked | No |
call.unparked |
A parked call was retrieved | No |
call.recording_started |
Call recording started | No |
call.recording_stopped |
Call recording stopped | No |
Subscribe only to the events your integration needs. Ringing events can be high volume on busy systems.
call.ringing and call.ringing_stopped may include multiple parties in one event when several extensions start or stop ringing at nearly the same time. Ringing payloads use ringingParties; stopped-ringing payloads use stoppedRingingParties, where each item includes the party and the stop reason.
A single ring attempt can still produce multiple call.ringing or call.ringing_stopped events. For example, callees that start or stop ringing at almost the same time may be grouped into one event, while callees that start or stop a little later may be sent in another event for the same ring attempt. Use ringAttempt to associate ringing events from the same attempt; its type is regular or agent, and its id identifies that attempt. Use callUuid to associate events with the broader call.
Event payload #
Each event is sent to your endpoint as an HTTP POST with a JSON body.
Common fields #
Every event includes these top-level fields:
| Field | Type | Description |
|---|---|---|
event |
string | Event name, such as call.answered |
eventId |
string | Unique ID for this event |
createdAt |
string | When the event was created (ISO 8601 UTC) |
accountId |
string | Account that owns the call |
domainName |
string | PBX domain name |
callUuid |
string | Unique ID for the call |
direction |
string | The call direction. Values: inbound, outbound, local |
dialedNumber |
object | First number dialed for the call. This stays stable even if the current destination changes |
caller |
object | Calling party |
destination |
object | Current call destination |
origin |
object | Original call context, included only when a transfer changes the call’s callUuid |
call |
object | Call state snapshot at event time |
Use callUuid to tie multiple events to the same call. Use eventId to deduplicate retries of the same event.
Party objects #
Many fields are party objects with this shape:
| Field | Type | Description |
|---|---|---|
type |
string | Party kind: did, extension, external, application, or unknown |
number |
string | Normalized number. External numbers are E.164 when possible; extensions stay as dialed |
rawNumber |
string | Original number before normalization |
name |
string | Display name when known |
Examples: caller, destination, answeredBy, hungUpBy, items in ringingParties, and stoppedRingingParties[].party.
Dialed number #
dialedNumber and origin.dialedNumber use this shape:
| Field | Type | Description |
|---|---|---|
number |
string | Normalized first dialed number |
rawNumber |
string | Original first dialed number before formatting |
Transfer origin #
When a transfer changes the call’s callUuid to the transferee UUID, every webhook event emitted with that callUuid includes an origin object that identifies the original call. If the transferee is the original caller, the call keeps the original caller’s callUuid and origin is not included.
| Field | Type | Description |
|---|---|---|
callUuid |
string | Original call UUID |
caller |
object | Original caller party |
destination |
object | Original destination party at transfer time |
dialedNumber |
object | Original first dialed number |
The current transferred call still uses the top-level callUuid, caller, and destination fields. call.transferred uses the top-level destination as the transfer target.
Call snapshot #
The call object reflects call state when the event was emitted:
| Field | Type | Description |
|---|---|---|
state |
string | Normalized call state, such as ringing, answered, or ended |
startedAt |
string | When the call started |
answeredAt |
string | When the call was answered, if known |
endedAt |
string | When the call ended, if known |
isRecording |
boolean | Whether the call is recording |
isHolding |
boolean | Whether any participant is on hold |
isParked |
boolean | Whether the call is parked |
Event-specific fields #
Only fields relevant to the event are included. Omitted fields are not present in the JSON.
| Event | Additional fields |
|---|---|
call.started |
Common fields only |
call.ringing |
ringAttempt, ringingParties |
call.ringing_stopped |
ringAttempt, stoppedRingingParties |
call.answered |
answeredAt, answeredBy |
call.missed |
missedBy, missedCallees, endedAt, endedReason, durationSeconds |
call.completed |
answeredAt, answeredBy, hungUpBy, endedAt, endedReason, durationSeconds, answeredDurationSeconds, talkDurationSeconds |
call.transferred |
Updated destination |
call.held |
heldBy |
call.resumed |
resumedBy |
call.parked |
parkingExtension |
call.unparked |
parkingExtension |
call.recording_started |
recording.active, recording.startedAt |
call.recording_stopped |
recording.active, recording.stoppedAt |
Ringing fields #
ringAttempt.type—regularoragentif it’s a callcenter call.ringAttempt.id— identifier shared by ringing events from the same ring attemptringingParties— array of parties that started ringing in this eventstoppedRingingParties— array of{ party, reason }objects for parties that stopped ringing
stoppedRingingParties[].reason is one of: missed, canceled, answered_elsewhere, rejected, busy, unavailable, or unknown.
Completion fields #
durationSeconds— total call length from start to endansweredDurationSeconds— time from answer to endtalkDurationSeconds— time from when the answered callee was bridged to endendedReason— user-friendly end reason, such asnormal,missed,canceled,rejected,busy,unavailable,answered_elsewhere,failed, orunknown
Examples #
call.started #
{
"event": "call.started",
"eventId": "f4b8c2e1-6a3d-4f1e-9b2c-8d7e6f5a4b3c",
"createdAt": "2026-07-09T18:30:00.000Z",
"accountId": "11111111-1111-4111-8111-111111111111",
"domainName": "tenant.example.com",
"callUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"direction": "inbound",
"dialedNumber": {
"number": "+18005551212",
"rawNumber": "8005551212"
},
"caller": {
"type": "external",
"number": "+12125551234",
"rawNumber": "2125551234",
"name": "Jane Caller"
},
"destination": {
"type": "did",
"number": "+18005551212",
"rawNumber": "8005551212",
"name": "Main Line"
},
"call": {
"state": "ringing",
"startedAt": "2026-07-09T18:30:00.000Z",
"isRecording": false,
"isHolding": false,
"isParked": false
}
}
call.ringing #
{
"event": "call.ringing",
"eventId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
"createdAt": "2026-07-09T18:30:01.000Z",
"accountId": "11111111-1111-4111-8111-111111111111",
"domainName": "tenant.example.com",
"callUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"direction": "inbound",
"dialedNumber": {
"number": "+18005551212",
"rawNumber": "8005551212"
},
"caller": {
"type": "external",
"number": "+12125551234",
"rawNumber": "2125551234",
"name": "Jane Caller"
},
"destination": {
"type": "did",
"number": "+18005551212",
"rawNumber": "8005551212",
"name": "Main Line"
},
"ringAttempt": {
"type": "regular",
"id": "orig-attempt-001"
},
"ringingParties": [
{
"type": "extension",
"number": "101",
"rawNumber": "101",
"name": "Sales"
},
{
"type": "extension",
"number": "102",
"rawNumber": "102",
"name": "Support"
}
],
"call": {
"state": "ringing",
"startedAt": "2026-07-09T18:30:00.000Z",
"isRecording": false,
"isHolding": false,
"isParked": false
}
}
call.answered #
{
"event": "call.answered",
"eventId": "b2c3d4e5-f6a7-4890-b123-456789abcdef",
"createdAt": "2026-07-09T18:30:15.000Z",
"accountId": "11111111-1111-4111-8111-111111111111",
"domainName": "tenant.example.com",
"callUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"direction": "inbound",
"dialedNumber": {
"number": "+18005551212",
"rawNumber": "8005551212"
},
"caller": {
"type": "external",
"number": "+12125551234",
"rawNumber": "2125551234",
"name": "Jane Caller"
},
"destination": {
"type": "did",
"number": "+18005551212",
"rawNumber": "8005551212",
"name": "Main Line"
},
"answeredAt": "2026-07-09T18:30:15.000Z",
"answeredBy": {
"type": "extension",
"number": "101",
"rawNumber": "101",
"name": "Sales"
},
"call": {
"state": "answered",
"startedAt": "2026-07-09T18:30:00.000Z",
"answeredAt": "2026-07-09T18:30:15.000Z",
"isRecording": false,
"isHolding": false,
"isParked": false
}
}
call.completed #
{
"event": "call.completed",
"eventId": "c3d4e5f6-a7b8-4901-c234-56789abcdef0",
"createdAt": "2026-07-09T18:32:00.000Z",
"accountId": "11111111-1111-4111-8111-111111111111",
"domainName": "tenant.example.com",
"callUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"direction": "inbound",
"dialedNumber": {
"number": "+18005551212",
"rawNumber": "8005551212"
},
"caller": {
"type": "external",
"number": "+12125551234",
"rawNumber": "2125551234",
"name": "Jane Caller"
},
"destination": {
"type": "did",
"number": "+18005551212",
"rawNumber": "8005551212",
"name": "Main Line"
},
"answeredAt": "2026-07-09T18:30:15.000Z",
"answeredBy": {
"type": "extension",
"number": "101",
"rawNumber": "101",
"name": "Sales"
},
"hungUpBy": {
"type": "external",
"number": "+12125551234",
"rawNumber": "2125551234",
"name": "Jane Caller"
},
"endedAt": "2026-07-09T18:32:00.000Z",
"endedReason": "normal",
"durationSeconds": 120,
"answeredDurationSeconds": 105,
"talkDurationSeconds": 100,
"call": {
"state": "ended",
"startedAt": "2026-07-09T18:30:00.000Z",
"answeredAt": "2026-07-09T18:30:15.000Z",
"endedAt": "2026-07-09T18:32:00.000Z",
"isRecording": false,
"isHolding": false,
"isParked": false
}
}
call.transferred with origin #
{
"event": "call.transferred",
"eventId": "d4e5f6a7-b8c9-4012-d345-6789abcdef01",
"createdAt": "2026-07-09T18:31:00.000Z",
"accountId": "11111111-1111-4111-8111-111111111111",
"domainName": "tenant.example.com",
"callUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
"direction": "local",
"dialedNumber": {
"number": "*9664",
"rawNumber": "*9664"
},
"caller": {
"type": "extension",
"number": "202",
"rawNumber": "202"
},
"destination": {
"type": "application",
"number": "*9664",
"rawNumber": "*9664"
},
"origin": {
"callUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"caller": {
"type": "extension",
"number": "201",
"rawNumber": "201"
},
"destination": {
"type": "extension",
"number": "202",
"rawNumber": "202"
},
"dialedNumber": {
"number": "202",
"rawNumber": "202"
}
},
"call": {
"state": "answered",
"startedAt": "2026-07-09T18:30:00.000Z",
"answeredAt": "2026-07-09T18:30:15.000Z",
"isRecording": false,
"isHolding": false,
"isParked": false
}
}
Use View deliveries to inspect events from the last 24 hours. Filter by callUuid when you need all retained deliveries for a specific call, such as exporting the full call event timeline from the UI.
Auth methods #
Choose how Dataphone authenticates outbound deliveries to your endpoint.
| Auth method | Outbound behavior |
|---|---|
| HMAC signature | Sends X-Webhook-Signature: v1=<hex> — HMAC-SHA256 of {timestamp}.{rawBody} |
| Bearer token | Sends Authorization: Bearer <token> |
| Basic auth | Sends Authorization: Basic <base64(username:password)> |
| None | No auth headers (delivery metadata headers are still sent) |
Additional auth types can be supported if your integration requires them. Contact Dataphone support to discuss options.
All deliveries also include:
X-Webhook-IdX-Webhook-EventX-Webhook-DeliveryX-Webhook-Timestamp
Custom headers from the form are merged into each request.
Your endpoint must accept POST with Content-Type: application/json and respond with HTTP 2xx within 5 seconds.
Retry behavior #
Retry settings are configured in the create/edit form.
| Retry mode | Behavior |
|---|---|
| Key call events | Retries call.started, call.answered, call.completed, and call.missed on failure. Other subscribed events are sent once. |
| All events | Retry every subscribed event on failure |
| Never retry | No retries |
Optional fields:
- Max attempts — cap total delivery attempts, from 1 to 5
- Backoff seconds — comma-separated delays between retries (for example
60, 300, 900)
Failed deliveries appear in delivery history with status failed, an error message, and a scheduled next retry when applicable.
Recommended workflow #
- Create the webhook with label, URL, events, directions, auth, and retry settings.
- Test using the test sequence modal. Confirm your server accepts the payloads, returns 2xx, and processes the event data as expected. Inspect delivery history for the full JSON.
- Adjust configuration if needed and re-test.
- Contact Dataphone support to enable the webhook for live delivery. Include the webhook label and confirm tests passed.
- Monitor delivery history after it is live. Contact support again if you need it disabled or updated during rollout.