# Webhook event reference

This reference describes the request that phone.systems(TM) 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](verify-webhook-requests.html). For delivery
and retries, see [Webhook
delivery and troubleshooting](webhook-delivery-and-troubleshooting.html).

## 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](verify-webhook-requests.html#ps3-webhooks-signature). |
| `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](#ps3-webhooks-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](../cloud-storage-integrations.html#ps3-cloud-storage-integrations). |
| `call.cdr.attachment_failed` | A recording, voicemail, or fax of a CDR could not be saved. |
| `call.cdr.transcript_created` | The [AI Call Insights](../../ai/ai-settings.html#ps3-ai-settings) 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](../../ai/ai-call-scoring.html#ps3-ai-call-scoring-settings) results of a CDR are ready. |
| `call.cdr.ai_processing_failed` | AI processing of a CDR failed. |
| `call.cdr.updated` | The [tags](../call-tags.html#ps3-settings-call-tags) or notes of a call change. |
| `call.cdr.resolved` | A lost call is [resolved](../../Call-analytics/call-history.html#ps3-call-history-resolve-lost-calls). |

## 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(TM) 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](../../Call-analytics/call-history.html#ps3-call-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](#ps3-webhooks-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`.

call.started

```
 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}
```


call.connected

```
 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}
```


call.ended (answered)

```
 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}
```


call.ended (not answered)

```
 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](../../Call-analytics/call-history.html#ps3-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](../../Call-analytics/call-history.html#ps3-call-direction). |
| `status` | String or `null` | `answered`, `lost`, or `resolved`. When a lost call is resolved later, `call.cdr.resolved` is sent. See [Status](../../Call-analytics/call-history.html#ps3-call-history-call-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](../../Call-analytics/call-history.html#ps3-call-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](#ps3-webhooks-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](../cloud-storage-integrations.html#ps3-cloud-storage-integrations) (phone.systems(TM) 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](../cloud-storage-integrations.html#ps3-cloud-storage-integrations). - `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](../../Call-analytics/call-history.html#ps3-call-history-ai) 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](../../Call-analytics/ai-call-scoring.html#ps3-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](../../Call-analytics/call-history.html#ps3-call-history-ai).

```
 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](../../Call-analytics/call-history.html#ps3-call-history). See [Call
tags](../call-tags.html#ps3-settings-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](../../Call-analytics/call-history.html#ps3-call-history-call-status): 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](../../app/call-history.html#ps3-appplication-call-history-lost-calls). - `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](../../Call-analytics/call-history.html#ps3-call-history-end-codes). `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. |

## Related resources

- [Set up and manage a webhook](set-up-and-manage-webhooks.html) - Add the
  webhook and check its delivery status.
- [Verify webhook requests](verify-webhook-requests.html) - Check the
  `X-PS-Signature` header of each request.
- [Webhook delivery and troubleshooting](webhook-delivery-and-troubleshooting.html) - Understand ordering, retries,
  and duplicates.
- [Call History](../../Call-analytics/call-history.html#ps3-call-history) - Review calls, end codes, statuses,
  tags, and notes in phone.systems(TM).
- [AI Call Insights](../../Call-analytics/call-history.html#ps3-call-history-ai) - Learn when transcripts and
  summaries are created.
