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

# Webhook subscription lifecycle: register through disable and re-enable

> The operator loop for ONE webhook subscription — registration, signing-secret handling, the retry ladder per delivery, failure classification, what auto-disable means, and the re-enable flow that gets you delivering again.

# Webhook subscription lifecycle

A webhook subscription is one row — a `(destination URL, events filter)` pair — that moves through a lifecycle: **registered → active → (on sustained failure or proven-dead response) disabled → re-enabled.** This page walks that loop from the operator's seat: what happens at each state, where to watch it, and the exact re-enable flow when a subscription has been taken out. Fill-in details that live on sibling pages are linked, not repeated — per-attempt wire semantics sit in [Webhook delivery semantics](/concepts/webhook-delivery-semantics) and the one-event-to-many-destinations topology sits in [Webhook fan-out and event sinks](/concepts/webhook-fan-out-and-event-sinks).

## 1. Register a subscription — scope, events, destination

A subscription is created either in the dashboard (**Settings → Webhooks → Add endpoint**) or by API (`POST /api/v1/webhooks`). Three fields set its contract:

* **`url`** — the HTTPS destination Orbit POSTs every matching event to. Production endpoints require HTTPS, and the URL is validated to block requests to internal addresses.
* **`events`** — the filter for what this subscription receives: exact types (`message.delivered`), prefix globs (`message.*`), or `*` for everything. The filter is tenant-scoped and evaluated at fan-out time — a subscription never sees a non-matching event.
* **`secret`** — your signing secret. Omit it and Orbit auto-generates one; the response returns the `whsec_...` cleartext exactly once.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/webhooks \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/orbit",
    "events": ["message.delivered", "message.failed", "call.completed"]
  }'
```

Scope is **one subscription per tenant per URL** — nothing is shared across tenants, and a subscription is independent of every other: endpoint A's bad day never slows or blocks endpoint B (the reason is the per-subscription fan-out topology in [Webhook fan-out and event sinks](/concepts/webhook-fan-out-and-event-sinks)). An optional `description`, custom headers, a per-endpoint `timeout_seconds` (1–30s), a transform config, or an agent scope can pin the subscription further — all are partial-update fields on `PATCH /api/v1/webhooks/{id}`.

## 2. Signing — the secret and its rotation

Every delivery is signed HMAC-SHA256 with your subscription's secret; the signature lands in `X-Orbit-Signature` (canonical) as `t=<unix>,v1=<hex>` over `<t>.<raw_body>`. Verify before you process — a failed check must drop the delivery before your handler runs, or spoofed payloads pollute your dedup set. Copy-paste verifiers for Node.js, Python, and Go: [Webhook security](/webhooks/security).

**Rotation is a first-class lifecycle event, not a recreate.** When you rotate the secret, Orbit opens a **dual-signing grace window**: new deliveries carry the new-secret signature and the previous-secret signature side-by-side (`X-Orbit-Signature-Next`), so your verifier keeps validating while you deploy the new key. The API exposes the full rotation loop as first-class calls — `POST /api/v1/webhooks/{id}/rotate-secret`, `/rotation/initiate`, `/rotation/complete`, and `/rotation/cancel` — so you can script it. Body-hash verification logic, the legacy `X-Devotel-Signature` back-compat header, and common verification failures are covered in [Webhook security](/webhooks/security) and [Troubleshooting signature failures](/webhooks/troubleshooting-signature-failures).

## 3. Delivery attempt grid — initial attempt vs. retry ladder

Each event against your subscription is delivered on a fixed grid: **one initial attempt plus 9 retries, 10 attempts total**, on an exponential backoff starting at 30 seconds and doubling each round with +0–20% jitter. Any `2xx` within 30 seconds (or within your per-endpoint `timeout_seconds`, if set below the platform 30s ceiling) is a success; everything else schedules a retry. If all 10 attempts fail, the event moves to your subscription's dead-letter queue roughly **4.3 hours** after the first attempt at nominal delays.

A failed-delivery timeline (endpoint returning `503` every attempt, no jitter):

| Attempt     | Wait before attempt | Cumulative elapsed         |
| ----------- | ------------------- | -------------------------- |
| 1 (initial) | —                   | 0                          |
| 2           | 30s                 | 30s                        |
| 3           | 60s                 | 90s                        |
| 4           | 120s                | 3.5 min                    |
| 5           | 240s                | 7.5 min                    |
| 6           | 480s                | 15.5 min                   |
| 7           | 960s                | 31.5 min                   |
| 8           | 1920s               | 63.5 min                   |
| 9           | 3840s               | 2.1 h                      |
| 10          | 7680s               | 4.25 h → dead-letter queue |

The per-attempt contract — ordering, idempotency windows on both directions, and the two re-delivery causes — is the full subject of [Webhook delivery semantics](/concepts/webhook-delivery-semantics); this page deliberately does not restate it.

## 4. Failure classification — what each response means

Orbit classifies your endpoint's response into buckets, and each bucket drives a different next step:

| Class                                  | Meaning                                                                         | Next step                                                                                   |
| -------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| **Success**                            | Any `2xx` within the timeout                                                    | Attempt ends; subscription stays active                                                     |
| **Retryable**                          | Timeout, `5xx`, retryable `4xx` (e.g. `400`, `422`)                             | Schedules the next attempt on the retry ladder                                              |
| **Proven-dead**                        | `401`, `403`, `404`, `410 Gone`                                                 | Skips retries entirely — event to dead-letter queue, subscription auto-disabled immediately |
| **Malformed/unreadable response body** | A response that cannot be read as a valid HTTP result (e.g. truncated mid-body) | Counted like a timeout — retried                                                            |

`410 Gone` doubles as the deliberate single-event skip: return it for an event type you have deprecated and you route that event straight to the dead-letter queue without retrying the ladder. Reserved response codes that mean "we will never succeed here" (`401`/`403`/`404`/`410`) are documented with the retry contract in [Webhook delivery semantics](/concepts/webhook-delivery-semantics).

## 5. Circuit-breaker: when Orbit stops delivering

Orbit runs a per-subscription circuit breaker over **consecutive** failures. Two triggers stop delivery:

* **Proven-dead response** — the first `401` / `403` / `404` / `410` fires instantly (one failure, not a streak).
* **\~50 consecutive retryable failures** — a sustained failure streak on otherwise-ordinary responses.

When the breaker trips, the subscription is **auto-disabled**: org admins are emailed, and subsequent matching events go **straight to the subscription's dead-letter queue** — nothing is retried in the background, and nothing is lost. The breaker state is per subscription; other subscriptions are untouched.

A disable is not delete. The endpoint row, its events filter, its signing secret, and its delivery history all remain — you are preserving the configuration to re-enable, not re-registering. (Contrast with `PATCH /api/v1/webhooks/{id}` with `active: false`, an explicit operator-driven deactivation that uses the same neutral "no deliveries" state but is your decision rather than Orbit's.)

## 6. Re-enable: the operator loop

The fix → confirm → re-enable loop is three steps. Never skip step 1 — a re-enabled subscription that immediately fails again starts a new consecutive-failure streak.

1. **Fix the root cause on your side.** Confirm your receiver returns `2xx` within the timeout for a representative event; check TLS validity if you suspect it; fix auth if the disable was a proven-dead response.
2. **Probe with a test fire.** Fire a synthetic event against the subscription and confirm a clean `2xx` round-trip: `POST /api/v1/webhooks/{id}/test` (or `/test-fire`) — no production traffic yet.
3. **Re-enable the subscription.** In the dashboard (**Settings → Webhooks**) toggle the endpoint back to active, or call `PATCH /api/v1/webhooks/{id}` with `active: true`:

```bash theme={null}
curl -X PATCH https://api.orbit.devotel.io/api/v1/webhooks/wh_abc123 \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "active": true }'
```

The subscription transitions back to **active** and new matching events resume delivery immediately.

After re-enable, drain the dead-letter backlog that accumulated while the subscription was disabled — requeue per delivery, or bulk:

```bash theme={null}
# List the dead-letter inubox
curl https://api.orbit.devotel.io/api/v1/webhooks/dlq \
  -H "X-API-Key: dv_live_sk_..."

# Requeue one delivery
curl -X POST https://api.orbit.devotel.io/api/v1/webhooks/dlq/dlv_abc123/requeue \
  -H "X-API-Key: dv_live_sk_..."
```

A requeued delivery re-enters the retry pipeline as a fresh attempt — which is why your receiver's dedup buffer must span the full 7-day dead-letter replay window, not just the initial retry schedule (see [Webhook delivery semantics](/concepts/webhook-delivery-semantics), section 1). The dashboard's **Developer → Webhooks → Dead-letter queue** has one-click parity with both calls.

## 7. Event catalogue discovery

You do not subscribe to a fixed enumerated list — you discover the catalogue. Three surfaces carry it depending on what you are doing:

* **While picking events in the dashboard** — the event picker on **Settings → Webhooks → Add endpoint** enumerates families (`message.*`, `call.*`, `flow.execution.*`, `verification.*`, `email.*`, and the rest) with names.
* **While integrating** — `GET /api/v1/webhooks/events` returns the canonical event catalogue programmatically, the same source the picker reads.
* **While reading docs** — [Webhook events](/webhooks/events) is the rendered catalogue, with payload shapes per event and per-family grouping; the [events reference](/reference/webhook-events) holds it as a flat list.

Subscribing `*` receives everything; a prefix glob (`message.*`) receives the family; an exact type receives only that type. Subscribing a name not in the catalogue returns `422` — typo'd names cannot silently persist.

## Where this fits

This page is the operator loop for ONE subscription; two sibling concept pages carry the model one level down and one level up:

* [Webhook delivery semantics](/concepts/webhook-delivery-semantics) — the per-attempt retry contract, ordering, both idempotency windows, and the dead-letter queue
* [Webhook fan-out and event sinks](/concepts/webhook-fan-out-and-event-sinks) — the one-event-to-many-destinations topology a single subscription sits inside
* [Webhooks overview](/webhooks/overview) — endpoint setup, envelope shape, signature headers
* [Webhook security](/webhooks/security) — HMAC verification, rotation windows, copy-paste verifiers
* [Event Sinks API](/api-reference/event-sinks) — a sink destination as an alternative to per-endpoint HTTP
