Webhooks push events to your server as they happen, so you don't have to poll. A webhook belongs to an organization.
Configuration is a dashboard action. Webhooks are created and managed in the Davi dashboard, not through the public API — the management scopes are first-party only, so a third-party token can't obtain them. This is deliberate: a webhook records no creating app, so two integrations on the same organization would otherwise overwrite each other's subscriptions. Per-app subscriptions are planned. What this page documents is the part you implement: receiving and verifying deliveries.
Event types
| Event | Fires when |
|---|---|
| activity.registered | A user registers for an activity |
| activity.checked_in | A user checks in / attends |
| activity.cancelled | A registration is cancelled |
| activity.deleted | An activity is deleted |
| membership.joined | A user joins an organization |
| membership.left | A user leaves an organization |
| membership.renewed | A membership renews |
| membership.expiring | A membership is about to expire |
| reward.redeemed | A reward is redeemed |
A webhook.test event is sent when you trigger a test delivery.
Subscribing
Create a webhook for your organization with the endpoint URL and the events you
want. The URL must be publicly reachable — private, loopback, and .local
addresses are rejected. On creation the signing secret is returned once; store
it securely (you can rotate it later).
Payload
Each delivery is a JSON body with a consistent envelope. event and timestamp
identify the event; organization_uuid / user_uuid give its context; and data
carries the event-specific fields (its shape depends on the event type):
{
"event": "activity.checked_in",
"timestamp": "2026-01-15T18:42:07Z",
"organization_uuid": "8f3c…",
"user_uuid": "b21a…",
"data": {
"wallet_address": "0x…",
"user_id": "b21a…",
"activity_id": "6d4e…",
"session_id": "1c7f…"
}
}
Each delivery also carries these headers:
| Header | Value |
|---|---|
| X-Webhook-Event | The event type |
| X-Webhook-Signature | sha256=<hex> HMAC signature of the raw body |
| X-Webhook-Timestamp | Unix timestamp (seconds) |
| X-Webhook-Delivery | Unique delivery ID |
Verifying signatures
Every delivery is signed with HMAC-SHA256 over the raw request body using your webhook secret. Always verify before trusting a payload, and compare in constant time.
import hashlib
import hmac
def verify(secret: str, raw_body: bytes, signature_header: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, rawBody, signatureHeader) {
const expected =
"sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader);
return a.length === b.length && timingSafeEqual(a, b);
}
Compute the signature over the exact bytes you received — don't re-serialize the JSON first, or the signature won't match.
Responding, retries, and deliveries
- Respond with a
2xxstatus quickly (deliveries time out after 30 seconds). Do heavy work asynchronously. - Failed deliveries are retried with increasing backoff (roughly at 0s, 1s, 5s,
30s, 2m, and 10m). A delivery is
pending,success,failed, orretrying. - You can inspect delivery history — status, response code, timings, and error — and send a test event from the dashboard.
Next
- Org-scoped tokens — required to manage webhooks.