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.

Response201 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.

Response200 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.v1
  • Huudis-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