ReguNav™ Docs

Webhooks

85+ event types · HMAC-SHA-256 signed envelopes · 5-step retry schedule · dead-letter queue.

Overview

ReguNav’s webhook dispatcher emits compliance-domain events from the platform’s outbox to subscriber URLs the moment they happen. The same machinery that records an event in the audit-trail fans it out to your registered endpoints. Inbound webhooks (Stripe, customer-side, and cloud event-bus pushes from AWS SNS / Azure Event Grid / GCP Pub/Sub / Cloudflare Logpush) are handled by the same worker atwebhooks.regunav.com.

Outbound delivery

Endpoint & method

The dispatcher POSTs JSON to every subscriber URL configured for an event type. Bodies are CloudEvents-1.0-compatible envelopes.

Headers

HeaderDescription
content-typeapplication/json
user-agentReguNav-Webhooks/1.0
x-regunav-signatureHMAC envelope: t=<unix_ts>,v1=<hex_sha256>
x-regunav-eventThe event name (e.g. tenant.created)
x-regunav-tenantTenant ID the event was emitted for
x-regunav-delivery-attemptInteger attempt number (1 on first try; max 5)

Signature verification

Reject any request whose signature does not match. The construction is identical to Stripe’s scheme so existing Stripe-verification helpers port directly:

signed_payload = "{timestamp}.{raw_request_body}"
expected_v1    = HMAC-SHA256(signed_payload, your_endpoint_secret)
header         = "t={timestamp},v1={expected_v1}"

Reference verifier (Node.js / Workers / Bun — crypto.subtle):

async function verifyReguNavSignature(rawBody, header, secret, toleranceSec = 300) {
  const m = header.match(/(?:^|,\s*)t=(\d+),\s*v1=([0-9a-f]{64})\b/i);
  if (!m) return false;
  const [, t, v1] = m;
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(t)) > toleranceSec) return false;
  const key = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"],
  );
  const buf = await crypto.subtle.sign(
    "HMAC",
    key,
    new TextEncoder().encode(`${t}.${rawBody}`),
  );
  const expected = [...new Uint8Array(buf)]
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
  let diff = 0;
  for (let i = 0; i < expected.length; i++) diff |= expected.charCodeAt(i) ^ v1.charCodeAt(i);
  return diff === 0;
}

Always verify against the raw request body, not a JSON round-tripped copy — key ordering and whitespace will change the digest.

Retries & DLQ

A delivery is considered successful when your endpoint returns a 2xx within the worker request budget. Anything else (non-2xx, network error, timeout) is retried on this schedule (seconds since the previous attempt):

  • Attempt 2 — 1s after attempt 1
  • Attempt 3 — 5s after attempt 2
  • Attempt 4 — 30s after attempt 3
  • Attempt 5 — 5min after attempt 4
  • Final — 1h after attempt 5

After 5 failed attempts the event is parked in the dead-letter queue. The DLQ is operator-visible from the admin console (admin.regunav.com → Audit-trail → Webhooks) and can be replayed manually. Events stay in the audit-trail unconditionally — the outbox is purely for fan-out.

Idempotency

Each delivery carries a stable event_id in the body. Treat retries as idempotent — persist the event_id on your side and skip duplicates. ReguNav guarantees at-least-once delivery, not exactly-once.

Event envelope

{
  "specversion": "1.0",
  "id":          "evt_01HK8A2…",
  "source":      "https://api.regunav.com/v1/tenants/tnt_…",
  "type":        "tenant.created",
  "datacontenttype": "application/json",
  "time":        "2026-05-25T11:42:03.612Z",
  "subject":     "tnt_acme",
  "tenantid":    "tnt_acme",
  "data": {
    "tenant_id":   "tnt_acme",
    "display_name":"Acme Corp",
    "plan":        "Growth",
    "region":      "EU"
  }
}

Event categories

The canonical event catalog lives atpackages/engines/src/webhook-events.ts — it is the single source of truth for what the platform emits. Categories at the time of writing:

tenant.*
tenant.created · tenant.updated · tenant.suspended · tenant.archived
ai_system.*
registered · classified · deployed · retired
framework.* / control.*
framework.activated · framework.deactivated · control.implemented · control.drift_detected
evidence.*
uploaded · matched · expired · revoked
fria.*
submitted · approved · rejected
assessment.* / audit.*
assessment.created · assessment.completed · audit.scheduled · audit.run.started · audit.run.completed · audit.finding.opened · audit.finding.remediated · audit.signoff.recorded
incident.*
opened · escalated · disclosed · closed
dsar.*
received · fulfilled · refused
vendor.*
added · scored · suspended
agent.*
invoked · completed · failed
report.*
generated · signed
policy.*
draft_created · transitioned · approved · published · retired
training.*
completed · expired
gpai_summary.*
published · updated
red_team.*
run_started · run_completed · finding_recorded
hierarchy
workspace.created · team.created · team.membership.added · team.membership.removed · principal.created · principal.delegated
billing.*
meter.recorded · invoice.issued · payment.received
kye.*
entity.state_changed · authority.granted · authority.revoked · attestation.published

Inbound endpoints

Stripe — POST webhooks.regunav.com/stripe

Verifies the stripe-signature header per Stripe’s spec (5-minute skew window, timing-safe HMAC-SHA256 comparison). Configure the endpoint URL and webhook secret in your Stripe Dashboard; the secret is read from theSTRIPE_WEBHOOK_SECRET environment binding.

Customer-side — POST webhooks.regunav.com/inbound/{tenantId}

For pushing events into ReguNav from your own systems. Per-tenant HMAC verification via the regunav-signature header. The receiver classifies each payload through the event-registry and lands it on the canonical event-kind for downstream processing.

Cloud event-bus — POST webhooks.regunav.com/cloud/{provider}/{tenantId}

Native receivers for AWS SNS message-signed deliveries, Azure Event Grid withaeg-sas-key validation, GCP Pub/Sub with OIDC JWT verification, and Cloudflare Logpush HMAC. This is the BYOC (bring-your-own-cloud) path — no customer credentials at rest.

Discovery endpoints

The public API exposes three machine-discoverable indexes so SDKs and downstream consumers can enumerate what’s available without scraping the docs.

  • GET api.regunav.com/v1/glossary — 760+ regulatory terms across all 24 framework dictionaries, with their crosswalk edges. Use this to power autocomplete and regulator-language explainers.
  • GET api.regunav.com/v1/search/kinds — the 20 searchable resource kinds backing native BM25 search at /v1/search. Returns the kind id, label, and indexed fields per kind.
  • GET api.regunav.com/v1/reports/types — the 13 stakeholder-report types you can render via POST /v1/reports/generate. Each entry carries the stakeholder audience, the PDF template id, and required input fields.

Subscribing

Subscriptions are managed through the standard rail atapi.regunav.com/v1/webhook-subscriptions — see the API reference for the full create / list / rotate-secret / delete shape. The TypeScript SDK exposes the same operations under regunav.webhookSubscriptions.*.

Operational notes

  • The dispatcher runs on Cloudflare Workers with a 30-second per-attempt budget; long-lived endpoints (>5s p99) should ACK quickly and process asynchronously.
  • Outbound IPs are not stable — rely on signature verification, not allow-listing.
  • Delivery state is observable at GET /v1/webhook-deliveries (per-tenant) and in the admin audit-trail (cross-tenant).
  • Replays are explicit operator actions; ReguNav never replays automatically once an event has been parked.