Endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/v1/webhook-subscriptions | Create a subscription. |
GET | /api/v1/webhook-subscriptions | List all subscriptions. |
GET | /api/v1/webhook-subscriptions/{id} | Get a single subscription. |
PATCH | /api/v1/webhook-subscriptions/{id} | Update a subscription. |
DELETE | /api/v1/webhook-subscriptions/{id} | Delete a subscription. |
POST /api/v1/webhook-subscriptions
Status: Live · Auth: Bearer or HMAC · Idempotent: No
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
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.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.Subscription payload version label. Defaults to the current stable value (
2026-04-01).Response fields
Stable subscription id. Also emitted in the
X-Webhook-Subscription-Id header on every delivery.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.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)
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)
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
"is_active": true to enable a subscription.
Fields accepted by PATCH:
| Field | Notes |
|---|---|
url | Replaces the destination URL. Must be HTTPS. |
events | Replaces the event allowlist. Use [] for all events. |
version | Replaces the subscription version label. |
is_active | Enables or disables delivery. Setting true also clears the failure counter. |
DELETE /api/v1/webhook-subscriptions/
Permanently delete a subscription. Deliveries in-flight may still complete. Returns200 OK with the standard envelope:
200 OK
Signature verification
Each subscription generates its own secret, returned once in thePOST 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:
.), 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).
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.| Case | Behavior |
|---|---|
Matching subscription succeeds with 2xx | Event delivery is marked delivered. |
Matching subscription returns 4xx | Not retried for that delivery. The event is marked failed if no matching subscription succeeded. |
Matching subscription returns 5xx or cannot be reached | Chert makes up to 3 total delivery attempts with short exponential backoff before recording the final failure. |
Subscription URL is https://dev-inbox | No outbound POST is attempted; the event is stored for GET /api/v1/dev-inbox. |
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
| Symptom | Cause | Fix |
|---|---|---|
| Signature always mismatches | Verifying against your tenant signing_secret | Use the per-subscription secret returned by POST /webhook-subscriptions |
| Signature mismatches intermittently | JSON middleware re-serialized the body before your verifier saw it | Capture the raw body bytes before any parser runs |
| Signature mismatches after a clock change | Server clock skewed > 5 min from Chert | Sync via NTP; we reject anything outside ±300 s |
| Header value parses as undefined | Reading X-Webhook-Signature case-sensitively in lowercase-normalising frameworks | Read x-webhook-signature (lowercase) — most Node/Python frameworks normalise this way |

