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 |
|---|---|
|
|
|
The signature of the request, in the format |
|
The ID of the event. It is the same as the |
|
The number of the delivery attempt, from |
|
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 |
|---|---|---|
|
String |
The unique ID of the event. Retries and repeated deliveries of the same
event have the same |
|
String |
The event type, for example |
|
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 |
|
String or |
The ID of the call. All live call events and CDR events of the same call
have the same |
|
Object |
The details of the event. The fields depend on |
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 asconversation,key_topics,scorecards, andtags, contain objects.Values are words, such as
incomingoranswered, 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 |
|---|---|
|
A call starts ringing. |
|
A call is answered. |
|
A call ends. |
|
A call detail record (CDR) is created. A call can have several CDRs. |
|
A recording, voicemail, or fax of a CDR is saved to your cloud storage. |
|
A recording, voicemail, or fax of a CDR could not be saved. |
|
The AI Call Insights transcript of a CDR is ready. |
|
The AI Call Insights summary of a CDR is ready. |
|
The AI Call Scoring results of a CDR are ready. |
|
AI processing of a CDR failed. |
|
The tags or notes of a call change. |
|
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, andcall.ended.An unanswered call produces
call.startedandcall.ended, withoutcall.connected.If a call is first reported when it is already answered,
call.startedandcall.connectedare sent with the samets.
Field |
Type |
Description |
|---|---|---|
|
Boolean |
|
|
String or |
In
|
|
String |
When the call started, in UTC (ISO 8601). |
|
String or |
When the call was answered. |
|
String or |
When the call ended. Set only in |
|
String or |
The type of the caller: |
|
String or |
The number of the caller. |
|
String or |
The name of the caller, when the network provides one. |
|
String or |
The type of the destination: |
|
String or |
The number that was called. It can change during a transfer. The value is the one that is current when the event is sent. |
|
String or |
Why the call ended, for example |
|
Number or |
The SIP response code that ended the call, for example |
|
String or |
The SIP reason phrase, for example |
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 |
|---|---|---|
|
String |
The ID of the CDR, shown as Call ID in Call
History. Follow-up
events refer to the CDR by this value in |
|
Number or |
The position of the CDR in the call, starting at |
|
Boolean |
|
|
String or |
|
|
String or |
|
|
String |
When this stage of the call started, in UTC (ISO 8601). |
|
String or |
When it was answered. |
|
String or |
When it ended. |
|
Number or |
The number of seconds the caller waited before the answer. |
|
Number or |
The number of seconds of conversation. |
|
String or |
The type of the caller: |
|
String or |
The number of the caller. |
|
String or |
The display name of the caller. |
|
String or |
The type of the destination: |
|
String or |
The number that was called. |
|
String or |
Why this stage of the call ended. See End reasons. |
|
Number or |
The SIP response code on the caller’s side. |
|
String or |
The SIP reason phrase. |
|
String or |
The STIR/SHAKEN attestation result: |
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 |
|---|---|---|
|
String |
The ID of the CDR. |
|
String |
|
|
String |
|
|
Number or |
The audio length in seconds. |
|
Number or |
The file size in bytes. |
|
String |
The name of the file in your cloud storage, in the format
|
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 |
|---|---|---|
|
String |
The ID of the CDR. |
|
String |
|
|
String |
Why the file was not saved:
|
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 |
|---|---|---|
|
String |
The ID of the CDR. |
|
Array |
The utterances of the call, in order. Each item has:
|
|
Number or |
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 |
|---|---|---|
|
String |
The ID of the CDR. |
|
String or |
A short summary of the call, in the language set for AI Call Insights. |
|
Array |
Short phrases that name what the call was about. Empty when there are none. |
|
String or |
The sentiment of the caller: |
|
String or |
The sentiment of the callee: |
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 |
|---|---|---|
|
String |
The ID of the CDR. |
|
Array |
The scorecards that were applied. Each item has:
|
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 |
|---|---|---|
|
String |
The ID of the CDR. |
|
String |
The step that failed: |
|
String |
Why processing failed:
|
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 |
|---|---|---|
|
String |
The ID of the CDR. |
|
Array |
The current tags. Each item has |
|
String or |
The current 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 |
|---|---|---|
|
String |
The ID of the lost CDR. |
|
String |
When the CDR was resolved, in UTC (ISO 8601). |
|
String |
How the CDR was resolved:
|
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 |
|---|---|
|
The call was processed. |
|
The call was connected to a live agent. |
|
The caller waited too long without being connected. |
|
The caller disconnected before the call was picked up. |
|
The call was picked up. |
|
The call was redirected to another destination. |
|
The call could not be redirected. |
|
The call was redirected to voicemail by the scheduler. |
|
The destination contact method was not registered, did not respond, or returned an error. |
|
The call was redirected too many times. |
|
A generic failure without a detailed reason. |
|
Too many concurrent calls. |
|
The account is blocked. |
|
PSTN termination is blocked. |
|
No outbound number is set for the calling device. |
|
The call failed because of a disconnect reason from PSTN forwarding. |
|
The call failed because of a billing issue with PSTN services. |
|
The number is incorrect. |