Webhooks

Huudis fires webhook events to URLs you control so you can react to identity changes in real time — users signing up, joining workspaces, enrolling MFA, granting OIDC consent.

This page is the API reference for webhooks: event envelope, signature recipe, retry policy, full event catalog. For managing subscriptions themselves, see Webhook subscriptions. For the portal walkthrough, see Portal → Webhooks.

Event payload shape

Every webhook is a POST to your endpoint with this body:

{
  "id": "evt_01KPG30TXM4N5Q8R1S4T6V8X0Y",
  "type": "huudis.user.created.v1",
  "createdAt": "2026-05-12T10:42:00.123Z",
  "data": {
    /* the resource that triggered the event */
  }
}
Field Notes
id Unique event ID. Use for deduplication on your side.
type huudis.<resource>.<action>.v<n> — versioned, so additive payload changes don't break consumers.
createdAt When the event fired, not when you received it.
data The resource the event is about. Shape depends on type.

The event ID is stable across retries — idempotently upsert against it.

Headers

Every delivery carries:

Content-Type: application/json
Huudis-Event-Type: huudis.user.created.v1
Huudis-Event-Id: evt_01KPG30TXM4N5Q8R1S4T6V8X0Y
Huudis-Delivery-Id: whdlv_01KPG30TXM…
Huudis-Signature: t=1747033200,v1=8c4a…hexhmac

The signature is HMAC-SHA256 over <t>.<raw-body> with your subscription's secret.

Signature verification

The SDKs ship a one-call helper:

// Node
import { verifyWebhook } from '@forjio/huudis-node/webhooks';

app.post('/webhooks/huudis', (req, res) => {
  try {
    const event = verifyWebhook(
      req.rawBody,                          // unparsed body bytes
      req.headers['huudis-signature'],
      process.env.HUUDIS_WEBHOOK_SECRET
    );
    // event is the parsed payload
    res.status(200).end();
  } catch {
    res.status(401).end();
  }
});
# Python
from huudis.webhooks import verify_webhook
event = verify_webhook(req.raw_body, req.headers["huudis-signature"], WEBHOOK_SECRET)
// Go
event, err := huudis.VerifyWebhook(body, r.Header.Get("Huudis-Signature"), secret)

Without the SDK:

  1. Parse the header. Huudis-Signature: t=<timestamp>,v1=<hex>.
  2. Construct the signed string: <timestamp>.<raw-body-bytes>.
  3. Compute HMAC-SHA256(secret, signed) — compare hex with v1. Use constant-time comparison.
  4. Reject if now - timestamp > 5 minutes (replay protection).

Retry policy

Failed deliveries (non-2xx, timeout, or your endpoint took longer than 30 seconds) 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 failed permanently. Use POST /v1/account/webhook-subscriptions/deliveries/:id/replay to manually retry.

Ordering

Deliveries are not strictly ordered. Retries especially can land after a later event's first attempt. Idempotently process by eventId and consult createdAt if you need happens-before.

Event catalog

Events use the pattern huudis.<resource>.<action>.v<n>. Current types (most reserved-but-not-yet-emitted — see per-event pages):

Users

Event When
huudis.user.created.v1 New Huudis user provisioned.
huudis.user.disabled.v1 User globally disabled.
huudis.user.enabled.v1 User re-enabled.

Sessions

Event When
huudis.session.created.v1 New session minted.
huudis.session.revoked.v1 Session or refresh token revoked.

Workspaces

Event When
huudis.account.created.v1 New workspace.
huudis.account.member_added.v1 User joined a workspace.
huudis.account.service_enabled.v1 Workspace opted into a new Forjio product.

OIDC

Event When
huudis.oidc.consent_granted.v1 User consented to an OIDC client for the first time.

IAM

Event When
huudis.iam.policy_attached.v1 Policy attached to a principal.

The live catalog (with descriptions) is at GET /v1/account/webhook-subscriptions/events/catalog.

Versioning

Event types carry a major version suffix (.v1). Additive changes (new fields, new event types) ship within the same major version. Breaking changes — field removal or renamed fields — bump to .v2. We'd announce a deprecation window of at least 6 months and run both versions in parallel.

Subscribe to the explicit versioned types you handle (huudis.user.created.v1); don't pattern-match on huudis.user.*.

Next