Get real-time notifications for phone calls on your DoubleTick phone number (PSTN), including calls your customers make to you and calls your agents make to customers.
PSTN calls use the same core call lifecycle event names as WhatsApp calls, so a single webhook handler can process both. However, PSTN events include additional channel-specific fields and have different behavior for missed or failed calls.
To receive these events, configure a webhook on your PSTN channel and select the events you want.
For WhatsApp call webhooks, see Call Webhooks.
Events
| Event | Fires when |
|---|---|
CALL_INCOMING_INITIATED | A customer calls your phone number and it starts ringing for an agent. |
CALL_OUTGOING_INITIATED | An agent places a call to a customer. |
CALL_ACCEPTED | The call connects — an agent answers an incoming call, or the customer answers an outgoing call. |
CALL_ENDED | A connected call is hung up by either side. |
CALL_MISSED | The call ends without being connected. |
Note: CALL_NOT_PICKED and CALL_PERMISSION_REPLY_RECEIVED are WhatsApp-only events and are not sent for PSTN calls.
How to Identify a PSTN Call
Every PSTN event includes the common call payload fields along with the following PSTN-specific fields.
| Field | Type | Description |
|---|---|---|
channel | string | Always "PSTN" for phone calls. Use this field to distinguish PSTN calls from WhatsApp calls. |
channelNumber | string | Your DoubleTick phone number that the call came in on or went out from. |
from | string | The caller's number. For incoming calls, this is the customer's number; for outgoing calls, it is your phone number. |
to | string | The recipient's number. For incoming calls, this is your phone number; for outgoing calls, it is the customer's number. |
declinedBy | string | On CALL_MISSED, identifies "AGENT" or "CUSTOMER" when the side responsible is known. Otherwise, it is an empty string. |
Important:
wabaNumberis always an empty string for PSTN calls. UsechannelNumberto identify your DoubleTick phone number.
For the common call payload fields, see Shared Payload Structure.
Field Population by Event (PSTN)
The following table describes how key fields are populated for PSTN events.
| Field | CALL_INCOMING_INITIATED | CALL_OUTGOING_INITIATED | CALL_ACCEPTED | CALL_ENDED | CALL_MISSED |
|---|---|---|---|---|---|
callDirection | INCOMING | OUTGOING | Either | Either | Either |
callStatus | INITIATED | INITIATED | IN_PROGRESS | END | MISSED or FAILED |
callStartedAt | "" | "" | Populated | Populated | "" |
callEndedAt | "" | "" | "" | Populated | Populated |
durationInSeconds | 0 | 0 | 0 | Populated | 0 |
pickedBy | "" | Populated | Populated | Populated | Populated for outgoing calls |
endedBy | "" | "" | "" | "" | "" |
recordingUrl | "" | "" | "" | Conditional | "" |
declinedBy | "" | "" | "" | "" | Conditional |
endedByis always an empty string for PSTN calls. To identify which side hung up an unconnected call, usedeclinedByonCALL_MISSEDwhen it is populated.
Understanding CALL_MISSED
CALL_MISSEDFor PSTN calls, CALL_MISSED means the call did not connect. It covers both incoming and outgoing calls.
| What happened | callDirection | callStatus |
|---|---|---|
| Agent did not answer or declined on their phone | INCOMING | MISSED |
| Customer hung up before an agent answered | INCOMING | MISSED |
| Customer did not answer or declined an outgoing call | OUTGOING | MISSED |
| Customer's number was busy or unreachable | OUTGOING | FAILED |
| Agent's own phone did not answer | OUTGOING | FAILED |
Phone networks may report a declined call and an unanswered call in the same way, so both can arrive as CALL_MISSED. Check declinedBy to identify which side hung up, when that information is available.
Use callDirection, callStatus, and declinedBy together to determine what happened.
Typical Event Sequences
| Scenario | Events, in order |
|---|---|
| Incoming call, answered | CALL_INCOMING_INITIATED → CALL_ACCEPTED → CALL_ENDED |
| Incoming call, not answered | CALL_INCOMING_INITIATED → CALL_MISSED |
| Outgoing call, customer answers | CALL_OUTGOING_INITIATED → CALL_ACCEPTED → CALL_ENDED |
| Outgoing call, customer does not answer | CALL_OUTGOING_INITIATED → CALL_MISSED |
Recordings
When call recording is enabled, CALL_ENDED includes a recordingUrl.
Fetch the recording the same way as a WhatsApp call recording. See Accessing Recording URLs.
PSTN recordings are .wav files. Always use the recording URL exactly as delivered.
The recordingUrl field may be an empty string when a recording is unavailable. Check that the URL is non-empty before attempting to fetch or display a recording.
Sample Payloads
The following examples illustrate the payloads for common PSTN call events.
Incoming Call Initiated
{
"event": "CALL_INCOMING_INITIATED",
"callId": "8f14e45f-ceea-4f6a-9c1b-2d3e4f5a6b7c",
"chatMessageId": "msg_01HX9pst001",
"chatId": "chat_pst001",
"wabaNumber": "",
"channel": "PSTN",
"channelNumber": "918000000000",
"from": "919876543210",
"to": "918000000000",
"customerNumber": "919876543210",
"customerName": "Rohan Mehta",
"customerId": "customer_pvgpZUh3wg",
"callDirection": "INCOMING",
"callStatus": "INITIATED",
"failedReason": "",
"durationInSeconds": 0,
"callStartedAt": "",
"callEndedAt": "",
"pickedBy": "",
"rejectedBy": "",
"endedBy": "",
"recordingUrl": "",
"deviceId": "",
"declinedBy": ""
}
Outgoing Call Initiated
{
"event": "CALL_OUTGOING_INITIATED",
"callId": "c9f0f895-fb98-4b9a-8e2d-1a2b3c4d5e6f",
"chatMessageId": "msg_01HX9pst002",
"chatId": "chat_pst002",
"wabaNumber": "",
"channel": "PSTN",
"channelNumber": "918000000000",
"from": "918000000000",
"to": "919876543210",
"customerNumber": "919876543210",
"customerName": "Rohan Mehta",
"customerId": "customer_pvgpZUh3wg",
"callDirection": "OUTGOING",
"callStatus": "INITIATED",
"failedReason": "",
"durationInSeconds": 0,
"callStartedAt": "",
"callEndedAt": "",
"pickedBy": "919111111111",
"rejectedBy": "",
"endedBy": "",
"recordingUrl": "",
"deviceId": "",
"declinedBy": ""
}
Call Accepted — Incoming Call Answered by Agent
{
"event": "CALL_ACCEPTED",
"callId": "8f14e45f-ceea-4f6a-9c1b-2d3e4f5a6b7c",
"chatMessageId": "msg_01HX9pst001",
"chatId": "chat_pst001",
"wabaNumber": "",
"channel": "PSTN",
"channelNumber": "918000000000",
"from": "919876543210",
"to": "918000000000",
"customerNumber": "919876543210",
"customerName": "Rohan Mehta",
"customerId": "customer_pvgpZUh3wg",
"callDirection": "INCOMING",
"callStatus": "IN_PROGRESS",
"failedReason": "",
"durationInSeconds": 0,
"callStartedAt": "2026-01-15T10:30:00.000Z",
"callEndedAt": "",
"pickedBy": "919111111111",
"rejectedBy": "",
"endedBy": "",
"recordingUrl": "",
"deviceId": "",
"declinedBy": ""
}
Call Ended — Outgoing Call with Recording
{
"event": "CALL_ENDED",
"callId": "c9f0f895-fb98-4b9a-8e2d-1a2b3c4d5e6f",
"chatMessageId": "msg_01HX9pst002",
"chatId": "chat_pst002",
"wabaNumber": "",
"channel": "PSTN",
"channelNumber": "918000000000",
"from": "918000000000",
"to": "919876543210",
"customerNumber": "919876543210",
"customerName": "Rohan Mehta",
"customerId": "customer_pvgpZUh3wg",
"callDirection": "OUTGOING",
"callStatus": "END",
"failedReason": "",
"durationInSeconds": 92,
"callStartedAt": "2026-01-15T11:00:00.000Z",
"callEndedAt": "2026-01-15T11:01:32.000Z",
"pickedBy": "919111111111",
"rejectedBy": "",
"endedBy": "",
"recordingUrl": "https://recordings.example.com/c9f0f895-fb98-4b9a-8e2d-1a2b3c4d5e6f.wav",
"deviceId": "",
"declinedBy": ""
}
Call Missed — Incoming Call Declined by Agent
{
"event": "CALL_MISSED",
"callId": "45c48cce-2e2d-4fbd-a1b2-c3d4e5f6a7b8",
"chatMessageId": "msg_01HX9pst003",
"chatId": "chat_pst003",
"wabaNumber": "",
"channel": "PSTN",
"channelNumber": "918000000000",
"from": "919876543210",
"to": "918000000000",
"customerNumber": "919876543210",
"customerName": "Rohan Mehta",
"customerId": "customer_pvgpZUh3wg",
"callDirection": "INCOMING",
"callStatus": "MISSED",
"failedReason": "",
"durationInSeconds": 0,
"callStartedAt": "",
"callEndedAt": "2026-01-15T10:33:10.000Z",
"pickedBy": "",
"rejectedBy": "",
"endedBy": "",
"recordingUrl": "",
"deviceId": "",
"declinedBy": "AGENT",
"isNewCaller": false,
"isChatAssigned": false
}
Notes
- Calls forwarded to another number by a forwarding rule do not send webhooks.
CALL_NOT_PICKEDandCALL_PERMISSION_REPLY_RECEIVEDare WhatsApp-only events and are not sent for PSTN calls.wabaNumberis empty for PSTN calls. UsechannelandchannelNumberto identify the PSTN channel and phone number.
Best Practices for PSTN
- Check
channel === "PSTN"to identify phone calls. Do not rely on an emptywabaNumber. - Read your DoubleTick phone number from
channelNumber. - Treat
CALL_MISSEDas an indication that the call did not connect. UsecallDirection,callStatus, anddeclinedByto understand the outcome. - To confirm that a call was answered, look for
CALL_ACCEPTED, or aCALL_ENDEDevent with a non-zerodurationInSeconds. - Use
callIdto correlate lifecycle events belonging to the same call. - Check that
recordingUrlis non-empty before fetching or displaying a recording.

