Skip to main content
Webhook subscriptions let you register, update, and delete webhook endpoints via the API. Each subscription has its own secret and can filter to a specific set of event types. You can keep multiple active subscriptions for the same tenant.

Endpoints


POST /api/v1/webhook-subscriptions

Status: Live · Auth: Bearer or HMAC · Idempotent: No
What this does. Validates that url is an HTTPS URL, generates a 32-byte random secret, and creates a webhook subscription. Returns the full subscription including secret (shown once — store immediately). Subsequent GET responses never include the secret. Multiple subscriptions per tenant are supported; each has its own secret and event filter. Create a new webhook subscription. The secret is shown once in the response — save it immediately.

Request body

string
required
HTTPS URL that Chert will POST events to. Must be publicly reachable. For local development, use https://dev-inbox to record events without sending outbound HTTP.
string[]
Event types to subscribe to. Omit this field, or send an empty array, to receive all events. Use message.received for real-time inbound replies. Legacy lead_reply is still available for summary-style reply notifications; reaction events may appear as reaction.added and reaction.removed.
string
Subscription payload version label. Defaults to the current stable value (2026-04-01).

Response fields

string
Stable subscription id. Also emitted in the X-Webhook-Subscription-Id header on every delivery.
string
Per-subscription HMAC secret. Shown once. Store it securely and use it to verify the X-Webhook-Signature (or x-chert-signature) header on inbound deliveries. See Verifying signatures.
boolean
Whether this subscription is currently enabled for delivery. Update via PATCH.

GET /api/v1/webhook-subscriptions

Status: Live · Auth: Bearer or HMAC · Idempotent: Yes (read-only)
What this does. Returns all webhook subscriptions for your tenant, ordered newest first. secret is never included. List all subscriptions for your tenant, ordered newest first. secret is not included in list or get responses.
200 OK
updated_at reflects the last metadata change (URL, events, is_active, etc).

GET /api/v1/webhook-subscriptions/

Status: Live · Auth: Bearer or HMAC · Idempotent: Yes (read-only)
What this does. Row lookup by id scoped to your tenant. Same shape as a list row. Return a single subscription. Same shape as a list row.

PATCH /api/v1/webhook-subscriptions/

Update one or more fields. Only the fields you include are modified.
Request
Patch "is_active": true to enable a subscription. Fields accepted by PATCH:

DELETE /api/v1/webhook-subscriptions/

Permanently delete a subscription. Deliveries in-flight may still complete. Returns 200 OK with the standard envelope:
200 OK

Signature verification

Each subscription generates its own secret, returned once in the POST response. Use that subscription secret to verify deliveries — not your tenant signing_secret. The two are different values. Every delivery carries both signature headers — verify whichever your stack reads more easily. Both compute the same HMAC:
The separator between timestamp and body is a period (.), not a colon. The body is the exact raw bytes Chert sent — do not parse-and-re-serialize before verifying (JSON middleware that mutates whitespace will silently invalidate the signature).
The X-Webhook-Subscription-Id header identifies which subscription triggered the delivery, useful when multiple subscriptions point to the same endpoint with different secrets. This is a webhook delivery header from Chert to your endpoint; it is separate from x-chert-tenant, which your application sends when calling Chert APIs.

Delivery and replay

Chert records every event before outbound delivery. If multiple active subscriptions match an event, Chert attempts each destination and records one delivery attempt per subscription on the event row. Use GET /api/v1/events to inspect delivery status and POST /api/v1/events/{id}/replay to re-deliver a stored event. To replay to one subscription, pass ?subscription_id=<subscription_id>.

Common pitfalls


See also