Webhook subscriptions
A webhook subscription tells Huudis: "when one of these event types fires in this workspace, POST it to this URL." Each subscription has its own signing secret used to HMAC-sign every outbound delivery so your endpoint can verify the request actually came from Huudis.
For the wire-level event envelope, signature recipe, retry policy, and event catalog, see the per-event pages under Webhook events. This page covers managing the subscriptions themselves.
All endpoints require a bearer admin JWT — see Authentication.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/account/webhook-subscriptions |
List subscriptions in the active workspace |
POST |
/v1/account/webhook-subscriptions |
Create a subscription (returns secret once) |
GET |
/v1/account/webhook-subscriptions/:id |
Retrieve one |
PATCH |
/v1/account/webhook-subscriptions/:id |
Update URL, events, description, active flag |
DELETE |
/v1/account/webhook-subscriptions/:id |
Delete |
POST |
/v1/account/webhook-subscriptions/:id/rotate-secret |
Mint a new signing secret |
GET |
/v1/account/webhook-subscriptions/:id/deliveries |
Recent delivery log |
POST |
/v1/account/webhook-subscriptions/deliveries/:id/replay |
Re-attempt a single delivery |
GET |
/v1/account/webhook-subscriptions/events/catalog |
List event types this Huudis instance can emit |
List subscriptions
GET /v1/account/webhook-subscriptions
Returns subscriptions for the active workspace, newest first. The secret is never included in list responses — only on create and rotate.
Response
{
"data": [
{
"id": "whsub_01KPG…",
"accountId": "acc_01KPG…",
"url": "https://api.acme.com/huudis-events",
"events": ["huudis.user.created.v1", "huudis.account.member_added.v1"],
"active": true,
"description": "Onboarding webhook for Acme CRM",
"createdAt": "2026-04-12T10:00:00.000Z",
"updatedAt": "2026-04-12T10:00:00.000Z"
}
]
}
Create a subscription
POST /v1/account/webhook-subscriptions
Request body
| Field | Type | Required | Description |
|---|---|---|---|
url |
string (HTTPS URL) | yes | Where to POST events. HTTP is rejected — HMAC is meaningless over cleartext. |
events |
string[] | yes | One or more event types from GET /events/catalog. Subscribing to an unknown type is allowed (forward-compatible) but obviously won't fire. |
description |
string (≤200) | no | Internal label. |
active |
boolean | no | Default true. Set false to pause without deleting. |
Response — 201 Created. Includes the secret field exactly once:
{
"data": {
"id": "whsub_01KPG…",
"accountId": "acc_01KPG…",
"url": "https://api.acme.com/huudis-events",
"events": ["huudis.user.created.v1"],
"active": true,
"description": null,
"secret": "whsec_kqM2…redacted…vR3p",
"createdAt": "2026-05-12T23:55:00.000Z",
"updatedAt": "2026-05-12T23:55:00.000Z"
}
}
Subsequent GET calls return the same row without the secret. Lost? Rotate.
Update a subscription
PATCH /v1/account/webhook-subscriptions/:id
Partial update. accountId, id, and secret are immutable here; rotate-secret is a separate endpoint.
Request body
| Field | Description |
|---|---|
url |
New target URL (HTTPS). |
events |
Replace the event list. |
description |
New label, or null to clear. |
active |
Pause / unpause. |
Delete a subscription
DELETE /v1/account/webhook-subscriptions/:id
Hard-deletes. The delivery log for past events stays around for 30 days for audit, but the subscription row is gone.
204 No Content.
Rotate the signing secret
POST /v1/account/webhook-subscriptions/:id/rotate-secret
Mints a new secret and returns the plaintext exactly once. The old secret stops working immediately.
Response — 200 OK. { "secret": "whsec_…" }.
After rotation, your endpoint must be updated to the new secret before the next delivery arrives (typically ≤5 seconds). Plan rotations for low-traffic windows; in-flight retries with the old secret will fail signature verification on your side.
List deliveries
GET /v1/account/webhook-subscriptions/:id/deliveries?limit=50&status=failed
Returns recent attempted deliveries for one subscription. Each row reflects one HTTP attempt to your URL.
Query parameters
| Param | Type | Description |
|---|---|---|
limit |
integer | Default 50, max 200. |
status |
pending | succeeded | failed |
Filter. |
Response
{
"data": [
{
"id": "whdlv_01KPG…",
"subscriptionId": "whsub_01KPG…",
"eventType": "huudis.user.created.v1",
"eventId": "evt_01KPG…",
"status": "succeeded",
"attempt": 1,
"httpStatus": 200,
"responseBodySnippet": "{\"ok\":true}",
"createdAt": "2026-05-12T23:56:00.000Z",
"deliveredAt": "2026-05-12T23:56:01.123Z",
"nextRetryAt": null,
"lastError": null
}
]
}
responseBodySnippet captures the first 1KB of your endpoint's response — useful for surfacing your own error messages back in the dashboard.
Replay a delivery
POST /v1/account/webhook-subscriptions/deliveries/:id/replay
Re-attempts a single delivery. Status flips back to pending, retry counter resets, and the row is picked up by the next delivery worker tick (typically ≤1 second). Use this to recover one failed event without disturbing the rest.
200 OK. { "replayed": true }.
To bulk-replay every failed delivery, loop the deliveries list with status=failed and call replay per row — there's no bulk endpoint by design (a runaway replay shouldn't be one click).
List event catalog
GET /v1/account/webhook-subscriptions/events/catalog
Lists the event types this Huudis instance is configured to emit, with brief descriptions. Useful for rendering the create-subscription picklist.
{
"data": [
{ "type": "huudis.user.created.v1", "description": "A new user signed up." },
{ "type": "huudis.account.member_added.v1", "description": "A user joined a workspace." },
…
]
}
The subscription object
| Field | Type | Description |
|---|---|---|
id |
string (whsub_…) |
Stable subscription ID. |
accountId |
string | Owning workspace. |
url |
string | HTTPS delivery target. |
events |
string[] | Subscribed event types. |
active |
boolean | Paused subscriptions don't deliver. |
description |
string | null | |
createdAt, updatedAt |
ISO 8601 |
The plaintext secret appears only on create and rotate.
Retry policy
Failed deliveries (non-2xx HTTP response, timeout, or HMAC verification you returned 4xx for) retry with exponential backoff:
| Attempt | Delay since previous |
|---|---|
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 8 hours |
| 7 | 24 hours |
After 7 failed attempts, the delivery is marked failed permanently and does not retry further. Use the replay endpoint to manually re-attempt.
Signature verification
Every delivery carries:
Huudis-Signature: t=<timestamp>,v1=<hex hmac>— HMAC-SHA256 of<timestamp>.<raw body>with the subscription's secret.Huudis-Event-Type: huudis.user.created.v1Huudis-Event-Id: evt_01KPG…Huudis-Delivery-Id: whdlv_01KPG…Content-Type: application/json
Verify the signature before parsing the body — the SDKs ship a verifyWebhook(rawBody, header, secret) helper that does this in one call. See per-event pages for examples.
Ordering and idempotency
Deliveries are not strictly ordered — in particular, retries can arrive after a later event's first attempt. The eventId is stable across retries; idempotently upsert against it on your side and you're safe.
Two deliveries for two different events that happened in quick succession may arrive in any order. If you need a happens-before guarantee, examine the event's createdAt rather than wall-clock arrival.
Events
Webhook subscriptions don't have events of their own (Huudis doesn't fire webhook events about webhook config — that would be turtles-all-the-way-down). The audit log captures CRUD via webhook.subscription.created, webhook.subscription.updated, webhook.subscription.deleted, webhook.subscription.secret_rotated.
Next
- Webhook events catalog — per-event pages with payload shapes.
- OIDC clients — the typical source of webhook-relevant events.
- Account → Audit — the complementary write-once feed.