Webhooks
Asiri delivers events via signed HTTPS POSTs to URLs you register per workspace. Use them for asynchronous workflows: a DSR transitioning state, a policy being published, or a subscription test from the dashboard.
Event catalog
| Event | Fires when |
|---|---|
webhook.test | A tenant admin tests a webhook subscription. |
dsr.created | A data subject request is opened. |
dsr.identity_verified | Requester identity verification is completed. |
dsr.assigned | A request receives an owner. |
dsr.transitioned | A request moves between states. |
dsr.extension_granted | A statutory extension is recorded. |
dsr.responded | A response package is delivered to the requester. |
dsr.completed | The request is closed. |
dsr.sla_warning | A response deadline is approaching. |
dsr.sla_breached | A response deadline has passed. |
policy.created | A policy record is created. |
policy.version_drafted | A new policy version is drafted. |
policy.submitted_for_review | A policy is submitted for review. |
policy.published | A policy version is published. |
policy.retired | A policy is retired. |
policy.assigned | A policy acknowledgement task is assigned. |
policy.acknowledged | A user acknowledges a policy. |
Every current event is delivered as event_version: 1.
Use the dedicated references when implementing a production subscriber:
- Webhook versioning explains
event_version,pinnedVersions, deprecation headers, and migration rules. - Events v1 lists the current v1 event data shapes.
Signature verification
Every delivery includes an X-Asiri-Signature header:
X-Asiri-Signature: t=1715260800,v1=4f3c...t is the Unix timestamp; v1 is HMAC_SHA256(secret, "v1.{t}.{rawBody}") in lowercase hex.
You must verify the signature before trusting the body.
Pseudocode:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = createHmac('sha256', secret).update(`v1.${parts.t}.${rawBody}`).digest('hex');
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Reject deliveries where the timestamp is older than 5 minutes — that’s the replay window.
Retry policy
Asiri retries non-2xx responses with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | 0 |
| 2 | 30s |
| 3 | 2min |
| 4 | 10min |
| 5 | 1h |
| 6 | 6h |
| 7 | 24h |
After the seventh failure we mark the endpoint disabled and email the workspace owner. Your endpoint must respond within 10 seconds — slower responses count as failures.
Versioning
Webhook payloads are versioned independently of the REST API. Every request includes:
X-Asiri-Event: event type, for exampledsr.created.X-Asiri-Event-Id: unique delivery event id.X-Asiri-Event-Version: integer event schema version.
The body uses the same versioned envelope:
{
"event_version": 1,
"event_type": "dsr.created",
"event_id": "evt_01HXYZ",
"occurred_at": "2026-05-15T12:00:00.000Z",
"tenant_id": "00000000-0000-4000-8000-000000000001",
"data": {
"requestId": "00000000-0000-4000-8000-000000000002",
"ref": "DSR-2026-0001"
}
}Breaking schema changes ship as a new integer version for the affected event type. Subscriptions receive the latest version unless they pin a tested version in the dashboard. Deprecated pinned versions surface warnings in admin before their retirement date.