---
title: "Register a delivery webhook"
method: POST
path: "/v1/webhooks"
tags: ["Delivery webhooks"]
---

# Register a delivery webhook

`POST /v1/webhooks`

## Request

- Base URL: `https://api.cadencercs.com`
- URL: `https://api.cadencercs.com/v1/webhooks`
- Auth: HTTP bearer

**We POST to you.** This is the surface for an application that has no process to keep running — a Cloud Function, a Lambda, anything that wakes on a request. `GET /v1/events` needs a held connection; a `flow` answers for you but cannot consult your own model. This does neither: it hands the event to your endpoint and lets you decide.

Not to be confused with `PUT /v1/agents/{id}/webhook`, which points *Google* at a URL instead of at Cadence and switches off flow handling entirely.

The signing secret is returned **once** and cannot be read back — it is derived from a server secret rather than stored. Rotate with `PATCH /v1/webhooks/{id} {"rotateSecret": true}`.

Delivery is at-least-once: one attempt plus six retries spanning about nine hours (~10s, 1m, 5m, 30m, 2h, 6h after each failure). Anything other than a 2xx is a failure; so is taking longer than 10s. `nextAttemptAt` is the earliest we will retry, not a promise — the loop wakes on an interval, so delivery lands at or shortly after it. After 20 consecutive events exhaust their retries the endpoint is disabled with a reason.

## Request body

- object
  - `url` string, required: A public `https://` endpoint. Loopback and private ranges are refused — the payload carries message bodies and phone numbers.
  - `events` string[]: Defaults to all six. An unknown name is rejected rather than silently subscribing to nothing. **Subscribe to `optin` if you cache consent**: `optout` sets your flag and nothing else clears it.
  - `brandId` string: Narrow to one brand. Omit for every brand on the account.
  - `description` string

## Response `201`

Registered. The secret is shown once.

- object: An endpoint **we POST to** when something happens on your conversations. The opposite direction from `PUT /v1/agents/{id}/webhook`, which points *Google* somewhere other than Cadence.
  - `id` string, required
  - `url` string, required: Your https:// endpoint.
  - `events` string[], required
  - `brandId` string, nullable: Null means every brand on the account.
  - `description` string, nullable
  - `active` boolean, required: False once disabled, whether by you or by repeated failure.
  - `health` object
    - `lastSuccessAt` string, nullable
    - `lastFailureAt` string, nullable
    - `lastError` string, nullable
    - `consecutiveFailures` number: Consecutive *events* that failed every retry — not attempts. One slow afternoon does not count.
    - `disabledAt` string, nullable
    - `disabledReason` string, nullable
  - `createdAt` string
  - `secret` string, required: Shown once. Sign with this.
  - `verify` string: How to verify, in one sentence.
  - `note` string
  - `nextStep` string

## Other responses

- `400`: Bad URL, or an unknown event name.
- `401`: Not signed in, and no account-linked API key was sent.
- `403`: A brand-scoped key tried to subscribe for another brand, or the key isn't linked to an account.
- `404`: No such brand on this account.
- `500`: Unexpected server error. The body is always JSON.
- `503`: `WEBHOOK_SIGNING_SECRET` is unset on this deployment.

---

[API](https://skmtc.dev/cadencercs/apis/cadence-api.md) · [All operations](https://skmtc.dev/cadencercs/apis/cadence-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cadencercs/apis/cadence-api/revisions/9438443c2081?raw)
