Receive real-time HTTPS notifications when provisioning events occur. Register endpoints, verify HMAC-SHA256 signatures, and manage delivery.
Outbound webhooks let you register HTTPS endpoints that receive a signed POST request each time a provisioning event occurs in your organisation. Use them to keep downstream systems — your HRMS, access control platform, or audit log — in sync without polling the API. Requires the webhooks scope.
user.created — A user was provisioned via SCIM or the Management API.user.updated — A user's profile attributes were changed.user.deactivated — A user was deactivated and blocked from signing in.user.reactivated — A previously deactivated user was reactivated.user.deleted — A user was permanently deprovisioned.room.member_added — A user was added to a room.room.member_removed — A user was removed from a room.room.member.synced — A room's full member list was replaced via a PUT sync operation.// POST /webhooks
{
"url": "https://ingest.example.com/whoot-events",
"events": ["user.created", "user.deactivated", "user.deleted"],
"description": "HRMS sync endpoint"
}
// Response — 201 Created
{
"id": "d4e5f6a7-b8c9-0d1e-a2b3-c4d5e6f7a8b9",
"url": "https://ingest.example.com/whoot-events",
"events": ["user.created", "user.deactivated", "user.deleted"],
"secret": "whs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"status": "active",
"description": "HRMS sync endpoint",
"created_at": "2026-05-19T10:30:00.000Z"
}secret field is returned only once in the creation response. Store it securely before closing the response — it cannot be retrieved later. You will use this secret to verify the signature of every incoming delivery.url (required) — Must be an HTTPS URL. Plain HTTP is not accepted.events (required) — An array of one or more event names from the list above.secret (optional) — Provide your own signing secret (minimum 16 characters), or omit to have one generated automatically.description (optional) — A human-readable label for the registration.GET /webhooks — List all webhook registrations for the organisation. Secrets are excluded from list and get responses.GET /webhooks/{id} — Get a single webhook registration.PATCH /webhooks/{id} — Update the URL, event subscriptions, status (active or disabled), or description.DELETE /webhooks/{id} — Permanently remove a webhook registration.POST /webhooks/{id}/test — Send a synthetic test event to the webhook URL and return the HTTP status and response body your endpoint returned.Every delivery includes two headers:
- X-Whoot-Signature — The HMAC-SHA256 signature of the raw request body, formatted as sha256=<hex>.
- X-Whoot-Event — The event name (e.g. user.created).
Verify the signature before trusting the payload. Use a constant-time comparison to prevent timing attacks.
// Node.js — verify a webhook signature
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
if (signatureHeader.length !== expected.length) return false;
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expected)
);
}All events share a common envelope. The data object contains the affected resource.
{
"event": "user.created",
"tenant_id": "f0e9d8c7-b6a5-4321-fedc-ba9876543210",
"tenant_slug": "acme",
"timestamp": "2026-05-19T10:30:00.000Z",
"data": {
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "carol@acme.com",
"display_name": "Carol Santos",
"active": true
}
}
}2xx range are treated as success. All other codes — including redirects — are treated as failure.last_trigger_status field on the webhook record reflects the outcome of the most recent delivery attempt (success or failed).Set status: "disabled" to pause deliveries without deleting the registration. Events fired while a webhook is disabled are not queued or replayed — they are silently dropped. Re-enable by setting status: "active" at any time.
// PATCH /webhooks/{id} — pause deliveries
{ "status": "disabled" }
// PATCH /webhooks/{id} — resume deliveries
{ "status": "active" }