> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook Endpoints

> Manage multiple webhook URLs with per-endpoint secrets and event filters.

The endpoint-based webhook system lets you register **multiple**
destinations per organization, each with its own secret, its own
status, and its own subscription to a subset of event types. This is
the recommended model for all new integrations.

Compare to the [legacy single-URL webhook](/api-reference/organizations#legacy-single-url-webhook),
which is kept for backward compatibility but only supports one URL per
org.

## Endpoints

| Method   | Path                                                 | Required role | Description                          |
| -------- | ---------------------------------------------------- | ------------- | ------------------------------------ |
| `GET`    | `/v1/developer/webhook-endpoints`                    | `admin+`      | List endpoints                       |
| `POST`   | `/v1/developer/webhook-endpoints`                    | `admin+`      | Create an endpoint                   |
| `PATCH`  | `/v1/developer/webhook-endpoints/{endpoint_id}`      | `admin+`      | Update label / URL / events / status |
| `DELETE` | `/v1/developer/webhook-endpoints/{endpoint_id}`      | `admin+`      | Delete an endpoint                   |
| `POST`   | `/v1/developer/webhook-endpoints/{endpoint_id}/test` | `admin+`      | Send a signed test delivery          |

## Endpoint object

```json theme={null}
{
  "id": "c4d5e6f7-...",
  "label": "Production — Call events",
  "url": "https://example.com/thunderphone/hook",
  "events": ["telephony.incoming", "telephony.complete"],
  "status": "active",
  "secret_hint": "a1b2…9f0e",
  "created_at": "2026-04-20T18:24:10.113Z",
  "updated_at": "2026-04-20T18:24:10.113Z"
}
```

| Field                      | Type            | Description                                                                                                                                                                 |
| -------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                       | UUID            | Endpoint id                                                                                                                                                                 |
| `label`                    | string          | Display name, 1–120 chars                                                                                                                                                   |
| `url`                      | string          | HTTPS URL; `http://localhost` allowed for dev                                                                                                                               |
| `events`                   | array of string | Subscribed event types (see [valid values](#valid-event-types)). Empty array subscribes to all events                                                                       |
| `status`                   | string          | `active`, `disabled` (manually paused), or `failing` (auto-set when a delivery exhausts its 24 h retry schedule without a single 2xx)                                       |
| `secret_hint`              | string          | First 4 and last 4 characters of the signing secret with an ellipsis (`a1b2…9f0e`) — enough to cross-reference the secret you saved locally without exposing the full value |
| `created_at`, `updated_at` | timestamp       |                                                                                                                                                                             |

<Note>
  The endpoint's full `secret` is returned **once** on creation and
  never again. Store it securely — if you lose it, delete the endpoint
  and recreate it.
</Note>

### Valid event types

`events` is validated against this exact set — values outside the list
return `400`. See [Events catalog](/webhooks/events) for each type's
payload shape.

* `telephony.incoming`, `telephony.complete`, `telephony.tool`
* `web.incoming`, `web.complete`, `web.tool`
* `call.graded`
* `issue.reported`
* `test-call.completed`
* `alert.triggered`

### Endpoint statuses

* `active` — deliveries flow normally.
* `disabled` — manually paused via `PATCH`. No requests are sent. We
  never change a `disabled` endpoint's status; flipping it back to
  `active` is always your call.
* `failing` — set automatically when a delivery to the endpoint burns
  through its entire retry schedule (8 attempts over 24 hours) without
  ever getting a 2xx. A failing endpoint receives no further traffic.
  Once the endpoint is fixed, `PATCH` its status back to `active`;
  deliveries whose retry schedule hasn't run out yet resume where they
  left off.

***

## List endpoints

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.thunderphone.com/v1/developer/webhook-endpoints \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY"
  ```
</CodeGroup>

Returns an array of [Endpoint objects](#endpoint-object).

***

## Create an endpoint

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "label":  "Production — Call events",
      "url":    "https://example.com/thunderphone/hook",
      "events": ["telephony.incoming", "telephony.complete"]
    }'
  ```

  ```python Python theme={null}
  result = requests.post(
      "https://api.thunderphone.com/v1/developer/webhook-endpoints",
      headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
      json={
          "label":  "Production — Call events",
          "url":    "https://example.com/thunderphone/hook",
          "events": ["telephony.incoming", "telephony.complete"],
      },
  ).json()
  secret = result["secret"]
  endpoint_id = result["id"]
  ```
</CodeGroup>

### Request fields

| Field    | Type   | Required | Description                                                                                                                           |
| -------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `label`  | string | yes      | 1–120 chars                                                                                                                           |
| `url`    | string | yes      | HTTPS URL (`http` allowed only for `localhost` / `127.0.0.1`)                                                                         |
| `events` | array  | no       | Empty/omitted subscribes to all events. Must use the values listed in [Valid event types](#valid-event-types); duplicates are removed |

Returns `201 Created` with the [Endpoint object](#endpoint-object) plus
an extra top-level `secret` field containing the raw signing key — a
48-character hex string:

```json theme={null}
{
  "id": "c4d5e6f7-…",
  "label": "Production — Call events",
  "url": "https://example.com/thunderphone/hook",
  "events": ["telephony.incoming", "telephony.complete"],
  "status": "active",
  "secret_hint": "a1b2…9f0e",
  "created_at": "2026-04-20T18:24:10.113Z",
  "updated_at": "2026-04-20T18:24:10.113Z",
  "secret": "a1b2c37e08d94f5b16a2c8d90e7f3a4b5c6d7e8f90a19f0e"
}
```

<Warning>
  `secret` is returned **only on creation**. Subsequent `GET` responses
  include only the `secret_hint`. Copy the full value to your secret
  manager before dismissing the response.
</Warning>

***

## Update an endpoint

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://api.thunderphone.com/v1/developer/webhook-endpoints/c4d5e6f7-... \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "label":  "Production — Call + Grade events",
      "events": ["telephony.incoming", "telephony.complete", "call.graded"]
    }'
  ```
</CodeGroup>

| Field    | Type   | Description                                                                               |
| -------- | ------ | ----------------------------------------------------------------------------------------- |
| `label`  | string |                                                                                           |
| `url`    | string |                                                                                           |
| `events` | array  |                                                                                           |
| `status` | string | `active` or `disabled`. Set `active` to re-enable an endpoint the server marked `failing` |

Returns `200 OK` with the updated [Endpoint object](#endpoint-object).

***

## Send a test delivery

Send a synthetic `webhook.test` event to one endpoint using the normal
delivery pipeline, including canonical JSON serialization,
`X-ThunderPhone-Signature`, delivery recording, and retry bookkeeping.
The test targets the selected endpoint regardless of its `events` filter.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints/c4d5e6f7-.../test \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY"
  ```
</CodeGroup>

The endpoint receives an envelope like:

```json theme={null}
{
  "data": {
    "message": "ThunderPhone webhook test",
    "sent_at": "2026-07-17T20:12:34.567890+00:00"
  },
  "event_id": "2ad6507c-7d19-4498-9b2d-7e8f944ab5a1",
  "type": "webhook.test"
}
```

The API returns `200 OK` after the first attempt, even if the destination
returns an error. Inspect `success`, `status`, `response_code`, and `error`
for the delivery outcome:

```json theme={null}
{
  "success": true,
  "event_id": "2ad6507c-7d19-4498-9b2d-7e8f944ab5a1",
  "event_type": "webhook.test",
  "status": "delivered",
  "response_code": 204,
  "error": ""
}
```

`webhook.test` is synthetic and cannot be added to an endpoint's `events`
subscription. If the first attempt fails, the delivery follows the same
retry schedule as normal event deliveries.

***

## Delete an endpoint

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE https://api.thunderphone.com/v1/developer/webhook-endpoints/c4d5e6f7-... \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY"
  ```
</CodeGroup>

Returns `204 No Content`. Delivery to the URL stops immediately;
in-flight retries are abandoned.

***

## Related

<CardGroup cols={2}>
  <Card title="Events catalog" icon="list" href="/webhooks/events">
    The full list of `events` values you can subscribe to.
  </Card>

  <Card title="Webhooks overview" icon="bolt" href="/webhooks/overview">
    Signature verification and delivery semantics.
  </Card>
</CardGroup>
