Skip to main content

Webhooks

Subscribe to events and receive signed HTTP POSTs at your own endpoints when things happen in your workspace. Manage endpoints in the dashboard under Settings → Developer / API → Webhooks.

Event catalog

EventWhen
message.createdA message is sent or received (any channel)
message.updatedA message is updated (e.g. delivery/read status)
conversation.createdA new conversation is opened
conversation.status_changedA conversation is resolved/closed/snoozed/reopened
contact.createdA contact is created
contact.updatedA contact is updated

Subscribe to specific events or to all events ("*").

Payload

Every delivery has this envelope (the data object varies by event type):

{
"id": "evt_0b9c...",
"type": "message.created",
"api_version": "2026-06-01",
"created_at": "2026-06-24T10:00:00Z",
"app_id": "66f1...",
"data": { "...": "the resource object" }
}

Headers

HeaderDescription
X-Relasi-Webhook-IdUnique event id — use it to dedupe.
X-Relasi-TimestampRFC3339 send time — reject if too old (replay guard).
X-Relasi-Signaturev1=<base64 HMAC-SHA256> over "<id>.<unix_ts>.<body>".
X-Relasi-EventThe event type, for quick routing.

Verifying the signature

Always verify before trusting a payload. The signed content is "<X-Relasi-Webhook-Id>.<unix_timestamp>.<raw_body>", keyed with the endpoint's signing secret (shown in the dashboard). Use a constant-time comparison and reject deliveries whose timestamp is more than ~5 minutes from now.

import crypto from "node:crypto";

// rawBody must be the exact bytes received (do not re-serialize).
function verifyRelasiWebhook(req, signingSecret) {
const id = req.headers["x-relasi-webhook-id"];
const tsHeader = req.headers["x-relasi-timestamp"];
const signature = req.headers["x-relasi-signature"]; // "v1=..."
const rawBody = req.rawBody;

// 1) Replay guard (5 min)
const ts = Math.floor(new Date(tsHeader).getTime() / 1000);
if (Math.abs(Date.now() / 1000 - ts) > 300) return false;

// 2) Recompute signature
const signed = `${id}.${ts}.${rawBody}`;
const expected =
"v1=" +
crypto.createHmac("sha256", signingSecret).update(signed).digest("base64");

// 3) Constant-time compare
const a = Buffer.from(signature);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Retries & reliability

  • Non-2xx responses (or timeouts over 10s) are retried with exponential backoff: ~1m, 5m, 30m, 2h, 5h, 10h — up to 8 attempts, then marked exhausted.
  • An endpoint that fails repeatedly is auto-disabled; re-enable it in the dashboard (which clears its failure count).
  • Inspect every attempt under Details → Recent deliveries, and use Redeliver to retry a specific event.
  • Endpoints should be idempotent: the same event may be delivered more than once. Dedupe on X-Relasi-Webhook-Id.