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

# Register a webhook endpoint

`POST /v1/webhooks`

## Request

- Base URL: `https://api.cueframe.ai`
- URL: `https://api.cueframe.ai/v1/webhooks`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.cueframe.ai/api/auth/mcp/authorize`, token URL `https://api.cueframe.ai/api/auth/mcp/token`, scopes: `webhooks:write`)
  - API key in header `PAYMENT-SIGNATURE`

Register a webhook so long-running jobs (render/compose/media) notify you instead of blocking on wait_job. Body: `url` (https, must pass SSRF checks) + `events` (see the WebhookEvent enum). DELIVERY: each event is a POST with body `{ event, timestamp, projectId?, data }` and Standard-Webhooks headers `webhook-id`, `webhook-timestamp`, `webhook-signature: v1,<base64>` (+ `x-cueframe-event`). The `secret` (`whsec_<base64>`) is returned ONCE here — verify the raw body with `new Webhook(secret).verify(rawBody, headers)`. ACTIVATION (synchronous, no separate step): on create, CueFrame POSTs a signed `webhook.verify` challenge; your endpoint must reply 2xx echoing `data.token` (raw body === token, or JSON `{token}`/`{challenge}`). On success the response is `status:"active"`; otherwise `status:"pending"` with `verification.reason` and the webhook receives NO events until it verifies (recover with POST /webhooks/:id/verify — no delete/recreate). Full contract + per-event `data` shapes: GET /webhooks/events.

## Request body

- WebhookCreateRequest: POST /webhooks body.
  - `url` string, uri, required
  - `events` WebhookEvent[], required
  - `settleMs` integer

## Response `201`

Webhook created (secret returned once).

- WebhookCreateResponse: POST /webhooks response (secret present exactly once).
  - `id` string, required
  - `url` string, uri, required
  - `events` WebhookEvent[], required
  - `settleMs` integer, required
  - `enabled` boolean, required
  - `status` 'active' | 'pending', required: active = ownership verified + receiving events; pending = challenge not echoed, inactive.
  - `secret` string, required: Standard Webhooks signing secret, format `whsec_<base64>`. Returned ONCE at create; rotate = delete + recreate. Every delivery carries `webhook-id`, `webhook-timestamp` (unix seconds), and `webhook-signature: v1,<base64>` (HMAC-SHA256 over `{id}.{timestamp}.{rawBody}`). Verify with any Standard-Webhooks library (`standardwebhooks` / `svix`): `new Webhook(secret).verify(rawBody, headers)`.
  - `verification` object: Present ONLY when ownership verification did not complete (status:"pending"). CueFrame POSTs a signed `webhook.verify` challenge whose `data.token` your endpoint must echo in a 2xx to activate; `reason` is one of token_not_echoed | non_2xx_<code> | unreachable | ssrf_blocked. A pending webhook receives no events — fix the receiver and POST /webhooks/:id/verify (no delete/recreate needed). See GET /webhooks/events for the full contract.
    - `status` 'pending', required
    - `reason` string, required

## Other responses

- `400`: Bad request — validation_error / invalid_cursor / invalid_query.
- `401`: Missing or invalid Authorization header.
- `402`: Payment required. Returned when the caller presents no API key on a priced operation: the body AND the `Payment-Required` header carry the x402 v2 challenge (accepted scheme, network, USDC asset, amount, payTo). Sign the EIP-3009 authorization and retry with a `PAYMENT-SIGNATURE` header. A KEYED caller sees this status instead when their plan lacks or has exhausted the feature (`billing_required`).
- `403`: Forbidden — permission insufficient.
- `404`: Resource not found.
- `422`: Unprocessable — invalid_composition / media_in_use / session_in_use / render_not_cancellable.
- `429`: Rate limited. Retry-After header present.
- `500`: Internal error.

---

[API](https://skmtc.dev/cueframe/apis/cueframe-api.md) · [All operations](https://skmtc.dev/cueframe/apis/cueframe-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cueframe/apis/cueframe-api/revisions/8f9bfe362b4a?raw)
