Webhooks
Webhooks let your systems react to changes in Elido without polling. Two endpoint kinds:
- Event endpoints — subscribe to specific event types
(
link.created,member.invited, etc.) - SIEM endpoints — firehose: every event in the workspace, plus
the full audit-log stream as
audit.eventpayloads
Creating an endpoint
From Settings → Webhooks, or via API:
curl -X POST https://api.elido.app/v1/webhooks/workspaces/1/webhooks \
-H "Authorization: Bearer $ELIDO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/elido",
"events": ["link.created", "link.deleted"],
"kind": "event"
}'Response includes a one-time secret:
{
"endpoint": { "id": 7, "url": "...", "events": [...] },
"secret": "whsec_a1b2c3d4..."
}Save the secret now — it’s never returned again. Use it to verify signatures on incoming deliveries.
Verifying signatures
Every delivery carries:
X-Webhook-Signature: v1=4f8a...
X-Webhook-Timestamp: 1735689600
X-Webhook-Event: link.created
X-Webhook-Delivery: 12345Compute HMAC-SHA256(secret, "{timestamp}.{raw_body}") and compare
against the value after v1= in constant time. Reject deliveries
older than 5 minutes (now - timestamp > 300).
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret: string, headers: Headers, rawBody: string) {
const sig = headers.get("X-Webhook-Signature")?.replace(/^v1=/, "");
const ts = headers.get("X-Webhook-Timestamp");
if (!sig || !ts) return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}Retries
Failed deliveries (anything that’s not 2xx) retry on exponential
backoff: 1 minute, 5 minutes, 15 minutes. After 3 attempts the
delivery is marked failed.
You can manually re-arm a failed (or already-delivered) attempt from Webhooks → endpoint → Retry, or via:
curl -X POST \
https://api.elido.app/v1/webhooks/workspaces/1/webhooks/7/deliveries/12345/retry \
-H "Authorization: Bearer $ELIDO_TOKEN"SIEM mode
A SIEM-kind endpoint receives every event in the workspace,
regardless of the per-event subscription filter. That includes both
operational events (link.created, member.invited, …) and the
audit-log firehose (audit.event).
Every audit-relevant action (BAA signed, member role changed, API key
revoked, …) emits an audit.event envelope:
{
"type": "audit.event",
"workspace_id": 1,
"data": {
"kind": "member.role_changed",
"target_type": "member",
"target_id": 42,
"actor_user_id": 7,
"ip": "1.2.3.4",
"user_agent": "...",
"metadata": { "from": "viewer", "to": "admin" },
"recorded_at": "2026-05-01T12:00:00Z"
},
"timestamp": "2026-05-01T12:00:00Z"
}Your SIEM ingester (Splunk HEC, Datadog Logs, custom relay) is responsible for translating into its native format.
Available event types
| Event | When |
|---|---|
link.created | A new link is created |
link.updated | A link’s destination, slug, or rules change |
link.deleted | A link is archived or hard-deleted |
workspace.created | A new workspace is provisioned |
workspace.updated | Workspace settings change |
member.invited | Someone is invited |
member.removed | A member is removed (or leaves) |
audit.event | Any audit-log entry — see SIEM mode above |