Webhook event reference#

This reference describes the request that phone.systems™ sends for each webhook event: its headers, the event envelope, and the data of every event type.

For how to check that a request is genuine, see Verify webhook requests. For delivery and retries, see Webhook delivery and troubleshooting.

Request headers#

Each event is sent as one HTTPS POST request with a JSON body.

Header

Description

Content-Type

application/json, UTF-8.

X-PS-Signature

The signature of the request, in the format t=<unix time>,v1=<hex HMAC-SHA256>. See How requests are signed.

X-PS-Event-Id

The ID of the event. It is the same as the id in the body and stays the same on every retry. Use it to skip a duplicate before you parse the body.

X-PS-Attempt

The number of the delivery attempt, from 1 to 10.

X-Request-Id

The ID of this delivery attempt. It changes on every attempt. Use it to match a request in your logs with the details of a failure on the Webhooks page.

Event envelope#

Every request body has the same top-level fields:

Field

Type

Description

id

String

The unique ID of the event. Retries and repeated deliveries of the same event have the same id. Use it to detect duplicates.

type

String

The event type, for example call.started. See Event types.

ts

String

When the event was produced, in UTC (ISO 8601) with milliseconds. Events are produced by more than one system, so do not sort events by ts.

global_tag

String or null

The ID of the call. All live call events and CDR events of the same call have the same global_tag. null when the call ID is unknown.

data

Object

The details of the event. The fields depend on type.

Rules for every data object:

  • Every field is always present. A field without a value is null, never missing.

  • Values are strings, numbers, booleans, or null. Only lists, such as conversation, key_topics, scorecards, and tags, contain objects.

  • Values are words, such as incoming or answered, not internal codes.

  • Times are in UTC (ISO 8601), for example 2026-10-01T09:34:57Z.

  • New fields and event types can be added. Ignore the ones your code does not recognize. Existing fields keep their meaning.

Event types#

Type

Sent when

call.started

A call starts ringing.

call.connected

A call is answered.

call.ended

A call ends.

call.cdr.created

A call detail record (CDR) is created. A call can have several CDRs.

call.cdr.attachment_created

A recording, voicemail, or fax of a CDR is saved to your cloud storage.

call.cdr.attachment_failed

A recording, voicemail, or fax of a CDR could not be saved.

call.cdr.transcript_created

The AI Call Insights transcript of a CDR is ready.

call.cdr.summary_created

The AI Call Insights summary of a CDR is ready.

call.cdr.scoring_created

The AI Call Scoring results of a CDR are ready.

call.cdr.ai_processing_failed

AI processing of a CDR failed.

call.cdr.updated

The tags or notes of a call change.

call.cdr.resolved

A lost call is resolved.

Live call events#

call.started, call.connected, and call.ended report a call while it is in progress. They have the same data fields.

  • An answered call produces call.started, call.connected, and call.ended.

  • An unanswered call produces call.started and call.ended, without call.connected.

  • If a call is first reported when it is already answered, call.started and call.connected are sent with the same ts.

Field

Type

Description

catch_up

Boolean

true when the event reports an earlier change late: for a call that was already in progress when you added the webhook, or after the delivery service restarted. false for events sent as the call happens. Skip catch_up events if you only need live changes.

reason

String or null

In call.ended, why the event was sent:

  • hangup — The call ended.

  • lost — The call was no longer reported and is considered ended. It may have ended up to 3 minutes earlier. ts is when it was considered ended.

  • moved — The call was moved to another account.

null in call.started and call.connected, or when the reason is unknown.

time_start

String

When the call started, in UTC (ISO 8601).

time_connect

String or null

When the call was answered. null if it was not answered.

time_end

String or null

When the call ended. Set only in call.ended.

src_type

String or null

The type of the caller: user (a call from a device of a phone.systems™ user) or incoming_trunk (a call to one of your numbers).

src_number

String or null

The number of the caller.

src_name

String or null

The name of the caller, when the network provides one.

dst_type

String or null

The type of the destination: user, pstn, call_flow, or call_pick_up. See Destination. Can be null until the call is answered.

dst_number

String or null

The number that was called. It can change during a transfer. The value is the one that is current when the event is sent.

end_reason

String or null

Why the call ended, for example Connected to agent or Failed. Set only in call.ended. See End reasons.

disconnect_code

Number or null

The SIP response code that ended the call, for example 200 or 486. Set only in call.ended.

disconnect_reason

String or null

The SIP reason phrase, for example Bye or Busy Here. Set only in call.ended.

A transfer can end one leg of the call and start another. A call.ended followed by a call.started with the same global_tag is then one conversation that changes legs, and the end fields of that call.ended are null.

 1{
 2  "id": "d75df7ee-5c1f-7a9f-b213-5cb13ccc38b7",
 3  "type": "call.started",
 4  "ts": "2026-10-01T09:34:57.221Z",
 5  "global_tag": "faf24793-bbd2-4ef0-a94c-6dcc2215207b",
 6  "data": {
 7    "catch_up": false,
 8    "reason": null,
 9    "time_start": "2026-10-01T09:34:57Z",
10    "time_connect": null,
11    "time_end": null,
12    "src_type": "user",
13    "src_number": "15551230001",
14    "src_name": "Alex Smith",
15    "dst_type": null,
16    "dst_number": "15551230002",
17    "end_reason": null,
18    "disconnect_code": null,
19    "disconnect_reason": null
20  }
21}
 1{
 2  "id": "1fc4be40-d2ed-7018-9bf9-952fe408726b",
 3  "type": "call.connected",
 4  "ts": "2026-10-01T09:34:58.393Z",
 5  "global_tag": "faf24793-bbd2-4ef0-a94c-6dcc2215207b",
 6  "data": {
 7    "catch_up": false,
 8    "reason": null,
 9    "time_start": "2026-10-01T09:34:57Z",
10    "time_connect": "2026-10-01T09:34:58Z",
11    "time_end": null,
12    "src_type": "user",
13    "src_number": "15551230001",
14    "src_name": "Alex Smith",
15    "dst_type": "pstn",
16    "dst_number": "15551230002",
17    "end_reason": null,
18    "disconnect_code": null,
19    "disconnect_reason": null
20  }
21}
 1{
 2  "id": "13f329c0-bbb0-7683-9286-c20e302f0ae0",
 3  "type": "call.ended",
 4  "ts": "2026-10-01T09:35:05.162Z",
 5  "global_tag": "faf24793-bbd2-4ef0-a94c-6dcc2215207b",
 6  "data": {
 7    "catch_up": false,
 8    "reason": "hangup",
 9    "time_start": "2026-10-01T09:34:57Z",
10    "time_connect": "2026-10-01T09:34:58Z",
11    "time_end": "2026-10-01T09:35:05Z",
12    "src_type": "user",
13    "src_number": "15551230001",
14    "src_name": "Alex Smith",
15    "dst_type": "pstn",
16    "dst_number": "15551230002",
17    "end_reason": "Connected to agent",
18    "disconnect_code": 200,
19    "disconnect_reason": "Bye"
20  }
21}
 1{
 2  "id": "7678cc69-2fcb-7832-b466-e705098a4df0",
 3  "type": "call.ended",
 4  "ts": "2026-10-01T09:38:42.663Z",
 5  "global_tag": "6c5fe71b-2d0a-456c-9fc8-3d7461ad5f4b",
 6  "data": {
 7    "catch_up": false,
 8    "reason": "hangup",
 9    "time_start": "2026-10-01T09:38:35Z",
10    "time_connect": null,
11    "time_end": "2026-10-01T09:38:42Z",
12    "src_type": "user",
13    "src_number": "15551230001",
14    "src_name": "Alex Smith",
15    "dst_type": null,
16    "dst_number": "15551230003",
17    "end_reason": "Failed",
18    "disconnect_code": 486,
19    "disconnect_reason": "Busy Here"
20  }
21}

CDR events#

CDR events report the call detail records (CDRs) of a call after it ends. call.cdr.created delivers each CDR. Follow-up events report later results for a CDR, such as files, AI Call Insights, tags, notes, and lost call resolution. Each follow-up event identifies its CDR by cdr_uuid, which is the uuid of the matching call.cdr.created event. Follow-up events are sent only when the related feature is enabled for your account.

Every follow-up event of a CDR arrives after its call.cdr.created. After that, each event is sent when its result is ready, so the kinds of events can mix. For example, a tag added while AI processing is running arrives as call.cdr.updated between the transcript and the summary, and a lost call can be resolved before its voicemail is saved. Only the AI events keep their order: transcript, then summary, then scoring.

call.cdr.created#

Sent for every call detail record (CDR) of a call as soon as it is written. It is always the first event of its CDR. A direct call has one CDR. A call that goes through a queue or is transferred has several, numbered by cdr_number. Group the CDRs of a call by global_tag and order them by cdr_number.

Field

Type

Description

uuid

String

The ID of the CDR, shown as Call ID in Call History. Follow-up events refer to the CDR by this value in cdr_uuid.

cdr_number

Number or null

The position of the CDR in the call, starting at 1.

final

Boolean

true for the last CDR of the call. It can arrive long after the other CDRs, or not at all, so do not wait for it before you process a call.

direction

String or null

incoming, outgoing, or internal. See Direction.

status

String or null

answered, lost, or resolved. When a lost call is resolved later, call.cdr.resolved is sent. See Status.

time_start

String

When this stage of the call started, in UTC (ISO 8601).

time_connect

String or null

When it was answered. null if it was not answered.

time_end

String or null

When it ended.

waiting_time

Number or null

The number of seconds the caller waited before the answer.

in_call_time

Number or null

The number of seconds of conversation.

src_type

String or null

The type of the caller: user or incoming_trunk. See Source.

src_number

String or null

The number of the caller.

src_name

String or null

The display name of the caller.

dst_type

String or null

The type of the destination: user, pstn, call_flow, or call_pick_up.

dst_number

String or null

The number that was called.

end_reason

String or null

Why this stage of the call ended. See End reasons.

disconnect_code

Number or null

The SIP response code on the caller’s side.

disconnect_reason

String or null

The SIP reason phrase.

stir_shaken_attestation

String or null

The STIR/SHAKEN attestation result: a, b, c, none, invalid, or null.

 1{
 2  "id": "48ed81e7-86ba-50f1-8a9b-6711a1e30e16",
 3  "type": "call.cdr.created",
 4  "ts": "2026-10-01T09:35:14.554Z",
 5  "global_tag": "faf24793-bbd2-4ef0-a94c-6dcc2215207b",
 6  "data": {
 7    "uuid": "ce0bd65b-6406-427d-90c9-5e2c411bff12",
 8    "cdr_number": 1,
 9    "final": true,
10    "direction": "outgoing",
11    "status": "answered",
12    "time_start": "2026-10-01T09:34:57Z",
13    "time_connect": "2026-10-01T09:34:58Z",
14    "time_end": "2026-10-01T09:35:05Z",
15    "waiting_time": 1,
16    "in_call_time": 7,
17    "src_type": "user",
18    "src_number": "15551230001",
19    "src_name": "Alex Smith",
20    "dst_type": "pstn",
21    "dst_number": "15551230002",
22    "end_reason": "Connected to agent",
23    "disconnect_code": 200,
24    "disconnect_reason": "Bye",
25    "stir_shaken_attestation": null
26  }
27}

Tags and notes are not part of this event. They are sent in call.cdr.updated.

A follow-up event that is ready before its CDR’s call.cdr.created waits up to about 5 minutes for it. If call.cdr.created is not sent by then, the follow-up event is not sent either.

call.cdr.attachment_created#

Sent when a recording, voicemail, or fax of a CDR is saved to your cloud storage (phone.systems™ cloud storage, AWS S3, S3-compatible storage, or Azure Blob Storage). One event is sent for each file. It can arrive some time after the call, because the recording is converted first and, with personal data redaction on, redacted. Recordings that are sent only to a CRM do not produce file events.

The event does not include a download link. Read the file from your cloud storage by its file_name.

Field

Type

Description

cdr_uuid

String

The ID of the CDR.

attachment_type

String

recording, voicemail, or fax.

format

String

mp3 for a recording or voicemail, pdf for a fax.

duration

Number or null

The audio length in seconds. null for a fax.

size

Number or null

The file size in bytes. null when unknown.

file_name

String

The name of the file in your cloud storage, in the format <cdr uuid>-<attachment type>.<format>. In your own AWS S3, S3-compatible, or Azure Blob Storage, the file is at the top level of the bucket or container.

 1{
 2  "id": "b2133a63-80f9-5ecf-8362-8ff75c31d441",
 3  "type": "call.cdr.attachment_created",
 4  "ts": "2026-10-01T09:35:31.208Z",
 5  "global_tag": "faf24793-bbd2-4ef0-a94c-6dcc2215207b",
 6  "data": {
 7    "cdr_uuid": "ce0bd65b-6406-427d-90c9-5e2c411bff12",
 8    "attachment_type": "recording",
 9    "format": "mp3",
10    "duration": 7.4,
11    "size": 58368,
12    "file_name": "ce0bd65b-6406-427d-90c9-5e2c411bff12-recording.mp3"
13  }
14}

call.cdr.attachment_failed#

Sent when an expected recording, voicemail, or fax of a CDR will not be available. One event is sent for each file.

Field

Type

Description

cdr_uuid

String

The ID of the CDR.

attachment_type

String

recording, voicemail, or fax.

reason

String

Why the file was not saved:

  • upload_error — The file could not be saved in your cloud storage, for example because its credentials are no longer valid. Check your cloud storage settings.

  • redaction_withheld — Personal data redaction is on and the file was not kept. It could not be redacted, or only the redacted voicemail of the call is kept.

  • unprocessable — The file could not be processed, for example because it was damaged or too large.

 1{
 2  "id": "f57cb482-042a-59bb-91a3-49917c41c41a",
 3  "type": "call.cdr.attachment_failed",
 4  "ts": "2026-10-01T09:35:31.208Z",
 5  "global_tag": "faf24793-bbd2-4ef0-a94c-6dcc2215207b",
 6  "data": {
 7    "cdr_uuid": "ce0bd65b-6406-427d-90c9-5e2c411bff12",
 8    "attachment_type": "recording",
 9    "reason": "upload_error"
10  }
11}

call.cdr.transcript_created#

Sent when AI Call Insights have transcribed a recorded CDR. It is sent only for calls that qualify: recorded, answered, long enough, and in a direction that is enabled for AI Call Insights. call.cdr.summary_created for the same CDR follows right after.

Field

Type

Description

cdr_uuid

String

The ID of the CDR.

conversation

Array

The utterances of the call, in order. Each item has:

  • speaker — caller, callee, or unknown when the speaker could not be identified.

  • start — When the utterance starts, in milliseconds from the beginning of the recording, or null.

  • end — When the utterance ends, in milliseconds, or null.

  • text — What was said. With personal data redaction on, personal data is replaced by placeholders, such as [PERSON_NAME].

callee_talk_ratio

Number or null

The callee’s share of the spoken time, in percent. The caller’s share is the rest.

An event larger than 1 MiB is not sent. In practice, this happens only with a very long transcript.

 1{
 2  "id": "90913a1f-3106-5483-a72d-2d7e36bf7fec",
 3  "type": "call.cdr.transcript_created",
 4  "ts": "2026-10-01T09:36:02.917Z",
 5  "global_tag": "faf24793-bbd2-4ef0-a94c-6dcc2215207b",
 6  "data": {
 7    "cdr_uuid": "ce0bd65b-6406-427d-90c9-5e2c411bff12",
 8    "conversation": [
 9      { "speaker": "callee", "start": 640, "end": 2310, "text": "Hello, this is Northwind Support." },
10      { "speaker": "caller", "start": 2900, "end": 5120, "text": "Hi, I am calling about my order." }
11    ],
12    "callee_talk_ratio": 45.12
13  }
14}

call.cdr.summary_created#

Sent right after call.cdr.transcript_created with a short summary, the key topics, and the sentiment of the party outside your organization.

Field

Type

Description

cdr_uuid

String

The ID of the CDR.

summary

String or null

A short summary of the call, in the language set for AI Call Insights.

key_topics

Array

Short phrases that name what the call was about. Empty when there are none.

caller_sentiment

String or null

The sentiment of the caller: positive, neutral, or negative.

callee_sentiment

String or null

The sentiment of the callee: positive, neutral, or negative.

Only the party outside your organization is analyzed: the caller on incoming calls and the callee on outgoing calls. The sentiment of the other party is always null. On internal calls, both are null.

 1{
 2  "id": "a43847ce-9780-5716-b3b2-599273144f18",
 3  "type": "call.cdr.summary_created",
 4  "ts": "2026-10-01T09:36:09.442Z",
 5  "global_tag": "faf24793-bbd2-4ef0-a94c-6dcc2215207b",
 6  "data": {
 7    "cdr_uuid": "ce0bd65b-6406-427d-90c9-5e2c411bff12",
 8    "summary": "The caller asked about the delivery date of an order. The agent confirmed the order and the expected delivery date.",
 9    "key_topics": ["Order status", "Delivery date"],
10    "caller_sentiment": null,
11    "callee_sentiment": "positive"
12  }
13}

call.cdr.scoring_created#

Sent once per CDR when AI Call Scoring has scored the call against every active scorecard that matches the call direction. It is sent after call.cdr.summary_created. If some scorecards could not be scored, the event contains the ones that were. If none could be scored, call.cdr.ai_processing_failed is sent instead.

Field

Type

Description

cdr_uuid

String

The ID of the CDR.

scorecards

Array

The scorecards that were applied. Each item has:

  • name — The name of the scorecard.

  • call_direction — The call direction that the scorecard applies to: inbound, outbound, both, or ai_agent.

  • total_score — The points awarded, the sum of the criteria.

  • max_total_score — The maximum possible points.

  • items — The criteria. Each has name, awarded_points (from 0 to max_points), max_points, reasoning, and coaching_tips (up to two suggestions for the agent, or empty).

 1{
 2  "id": "ad4f23b9-d437-5f60-bb91-e6b01f491554",
 3  "type": "call.cdr.scoring_created",
 4  "ts": "2026-10-01T09:36:21.105Z",
 5  "global_tag": "faf24793-bbd2-4ef0-a94c-6dcc2215207b",
 6  "data": {
 7    "cdr_uuid": "ce0bd65b-6406-427d-90c9-5e2c411bff12",
 8    "scorecards": [
 9      {
10        "name": "Customer support",
11        "call_direction": "inbound",
12        "total_score": 8,
13        "max_total_score": 10,
14        "items": [
15          {
16            "name": "Greeting",
17            "awarded_points": 5,
18            "max_points": 5,
19            "reasoning": "The agent greeted the caller and introduced the company.",
20            "coaching_tips": []
21          },
22          {
23            "name": "Resolution",
24            "awarded_points": 3,
25            "max_points": 5,
26            "reasoning": "The agent answered the question but did not confirm next steps.",
27            "coaching_tips": ["Summarize the next steps before ending the call."]
28          }
29        ]
30      }
31    ]
32  }
33}

call.cdr.ai_processing_failed#

Sent when AI processing was expected for a recorded CDR, but a step produced no result. It tells you that the transcript, summary, or scoring you are waiting for will not arrive, and why. It is never sent for calls that AI processing does not apply to, for example when AI Call Insights are disabled, the call was lost, or its direction is not enabled.

Field

Type

Description

cdr_uuid

String

The ID of the CDR.

stage

String

The step that failed: transcript, summary, or scoring.

reason

String

Why processing failed:

  • too_short — The recording is shorter than AI Call Insights require.

  • low_balance — The balance did not cover processing the call.

  • too_large — The recording is too large to process.

  • no_speech — The recording contains no speech.

  • redaction_withheld — The recording was withheld because personal data could not be redacted.

  • transcript_timeout — The transcript was not ready in time.

  • error — Something else went wrong. New kinds of failures are also sent as error.

For the conditions of AI processing, such as the minimum call length and the maximum file size, see AI Call Insights.

 1{
 2  "id": "94aa71c6-abc2-5062-b12b-e2a9703585e3",
 3  "type": "call.cdr.ai_processing_failed",
 4  "ts": "2026-10-01T09:35:40.731Z",
 5  "global_tag": "faf24793-bbd2-4ef0-a94c-6dcc2215207b",
 6  "data": {
 7    "cdr_uuid": "ce0bd65b-6406-427d-90c9-5e2c411bff12",
 8    "stage": "summary",
 9    "reason": "too_short"
10  }
11}

call.cdr.updated#

Sent when the tags or the notes of a call change in Call History. See Call tags. Tags and notes belong to the whole call, so one event is sent for each CDR of the call. Saving the same tags and notes again sends no event. It can arrive at any point after the CDR’s call.cdr.created, also between its file and AI events.

Field

Type

Description

cdr_uuid

String

The ID of the CDR.

tags

Array

The current tags. Each item has name. Empty when the call has no tags.

notes

String or null

The current notes. null when the call has no notes.

tags and notes are the complete current state, not the change. Two quick edits can both contain the second state, so keep the event with the latest ts.

 1{
 2  "id": "3b22e7de-dc4b-5c23-9697-b18c50060e38",
 3  "type": "call.cdr.updated",
 4  "ts": "2026-10-01T09:41:12.380Z",
 5  "global_tag": "faf24793-bbd2-4ef0-a94c-6dcc2215207b",
 6  "data": {
 7    "cdr_uuid": "ce0bd65b-6406-427d-90c9-5e2c411bff12",
 8    "tags": [{ "name": "VIP" }],
 9    "notes": "Customer asked for a callback tomorrow."
10  }
11}

call.cdr.resolved#

Sent when a lost call is resolved: the caller calls again, a user calls back, or someone resolves it manually. One resolution can cover several lost CDRs of the same caller, and each gets its own event. The global_tag is the one of the lost call. The event can arrive at any point after the CDR’s call.cdr.created, also before a voicemail event of the same CDR.

Field

Type

Description

cdr_uuid

String

The ID of the lost CDR.

resolved_at

String

When the CDR was resolved, in UTC (ISO 8601).

resolved_by_type

String

How the CDR was resolved:

  • call — A later call resolved it.

  • user — A user resolved it in Call History or the mobile app.

  • admin — It was resolved without a user, for example by DIDWW Support.

 1{
 2  "id": "178d1501-3f49-596b-9907-7d6a94fc0f4a",
 3  "type": "call.cdr.resolved",
 4  "ts": "2026-10-01T09:52:03.118Z",
 5  "global_tag": "6c5fe71b-2d0a-456c-9fc8-3d7461ad5f4b",
 6  "data": {
 7    "cdr_uuid": "73df01f8-7ad9-4428-9356-2f72bf9b4438",
 8    "resolved_at": "2026-10-01T09:52:02Z",
 9    "resolved_by_type": "user"
10  }
11}

End reasons#

end_reason in call.ended and call.cdr.created uses the end reasons shown in Call History. null means the reason is unknown. New values can be added.

Value

Description

Processed

The call was processed.

Connected to agent

The call was connected to a live agent.

Waiting timeout

The caller waited too long without being connected.

Disconnect from originator

The caller disconnected before the call was picked up.

Pick-up

The call was picked up.

Redirect

The call was redirected to another destination.

Redirect Failed

The call could not be redirected.

Not allowed by scheduler

The call was redirected to voicemail by the scheduler.

User not available

The destination contact method was not registered, did not respond, or returned an error.

Too many redirects

The call was redirected too many times.

Failed

A generic failure without a detailed reason.

Channels overload

Too many concurrent calls.

Account blocked

The account is blocked.

PSTN termination blocked

PSTN termination is blocked.

External calls are not allowed

No outbound number is set for the calling device.

PSTN Termination error

The call failed because of a disconnect reason from PSTN forwarding.

PSTN Termination billing error

The call failed because of a billing issue with PSTN services.

Wrong number

The number is incorrect.