PSTN Call webhooks

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

EventFires when
CALL_INCOMING_INITIATEDA customer calls your phone number and it starts ringing for an agent.
CALL_OUTGOING_INITIATEDAn agent places a call to a customer.
CALL_ACCEPTEDThe call connects — an agent answers an incoming call, or the customer answers an outgoing call.
CALL_ENDEDA connected call is hung up by either side.
CALL_MISSEDThe 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.

FieldTypeDescription
channelstringAlways "PSTN" for phone calls. Use this field to distinguish PSTN calls from WhatsApp calls.
channelNumberstringYour DoubleTick phone number that the call came in on or went out from.
fromstringThe caller's number. For incoming calls, this is the customer's number; for outgoing calls, it is your phone number.
tostringThe recipient's number. For incoming calls, this is your phone number; for outgoing calls, it is the customer's number.
declinedBystringOn CALL_MISSED, identifies "AGENT" or "CUSTOMER" when the side responsible is known. Otherwise, it is an empty string.

Important: wabaNumber is always an empty string for PSTN calls. Use channelNumber to 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.

FieldCALL_INCOMING_INITIATEDCALL_OUTGOING_INITIATEDCALL_ACCEPTEDCALL_ENDEDCALL_MISSED
callDirectionINCOMINGOUTGOINGEitherEitherEither
callStatusINITIATEDINITIATEDIN_PROGRESSENDMISSED or FAILED
callStartedAt""""PopulatedPopulated""
callEndedAt""""""PopulatedPopulated
durationInSeconds000Populated0
pickedBy""PopulatedPopulatedPopulatedPopulated for outgoing calls
endedBy""""""""""
recordingUrl""""""Conditional""
declinedBy""""""""Conditional

endedBy is always an empty string for PSTN calls. To identify which side hung up an unconnected call, use declinedBy on CALL_MISSED when it is populated.


Understanding CALL_MISSED

For PSTN calls, CALL_MISSED means the call did not connect. It covers both incoming and outgoing calls.

What happenedcallDirectioncallStatus
Agent did not answer or declined on their phoneINCOMINGMISSED
Customer hung up before an agent answeredINCOMINGMISSED
Customer did not answer or declined an outgoing callOUTGOINGMISSED
Customer's number was busy or unreachableOUTGOINGFAILED
Agent's own phone did not answerOUTGOINGFAILED

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

ScenarioEvents, in order
Incoming call, answeredCALL_INCOMING_INITIATED → CALL_ACCEPTED → CALL_ENDED
Incoming call, not answeredCALL_INCOMING_INITIATED → CALL_MISSED
Outgoing call, customer answersCALL_OUTGOING_INITIATED → CALL_ACCEPTED → CALL_ENDED
Outgoing call, customer does not answerCALL_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_PICKED and CALL_PERMISSION_REPLY_RECEIVED are WhatsApp-only events and are not sent for PSTN calls.
  • wabaNumber is empty for PSTN calls. Use channel and channelNumber to identify the PSTN channel and phone number.

Best Practices for PSTN

  • Check channel === "PSTN" to identify phone calls. Do not rely on an empty wabaNumber.
  • Read your DoubleTick phone number from channelNumber.
  • Treat CALL_MISSED as an indication that the call did not connect. Use callDirection, callStatus, and declinedBy to understand the outcome.
  • To confirm that a call was answered, look for CALL_ACCEPTED, or a CALL_ENDED event with a non-zero durationInSeconds.
  • Use callId to correlate lifecycle events belonging to the same call.
  • Check that recordingUrl is non-empty before fetching or displaying a recording.