Operate
Webhooks
Send Sendora-originated events to your endpoints with HMAC signature + retry + SSRF guard + dead-letter on persistent failure.
Features
- **One subscription, whole taxonomy** — 50+ canonical event types across auth, push, email, sms, links, surveys, automation, billing, support, csat, consent, attribution. Customer subscribes once + their mirror stays in sync.
- **HMAC-SHA256 signing** — `t=…,v1=…` header (Stripe convention) so SDKs your team already wrote against Stripe slot in unchanged.
- **Exponential backoff** (2s / 8s / 30s / 90s) + permanent-vs-transient HTTP status classification — 4xx (except 408/429) goes permanent fast; 5xx / network / 429 retry.
- **Per-endpoint event filters** — subscribe to `auth.*` only, `email.bounced` only, whatever. Multiple endpoints per org, each with its own filter.
- **Delivery log + one-click replay** — every attempt logged with status code + response body + reconstructed signed-header line. Failed deliveries replay individually or in bulk by event filter.
- **Inbound endpoints with SSRF guard** — accept provider callbacks (Stripe, GitHub, Slack) without standing up your own validator stack. `lib/url-safety.ts` blocks RFC1918, loopback, link-local, cloud-metadata IPs (169.254.169.254, etc.), CGN ranges. Replay-window guard built in.
- **Rotating signing secret** — endpoint can hold two active secrets during rotation, both verify, then retire the old one. No webhook outage during key rotation.
Common use cases
- Replace Svix ($490+/mo Pro) — the delivery posture is identical (HMAC + exp backoff + replay), the event source ships with it.
- Replace Hookdeck (~$200/mo Mid) — same posture, plus you don't have to ship product events into it first.
- Mirror Sendora identity / messaging / support into your DB — `auth.device_takeover` deletes the right row, `email.bounced` updates the right user trait, `csat.detractor` flags the right account.
- Accept Stripe / GitHub / Slack callbacks without a separate validator service.
- Cross-tool wiring — Slack alerts on detractor CSAT, Discord pings on ticket SLA breach, PagerDuty on auth.signin_storm.
Key concepts
- HMAC signature
- `X-Sendora-Signature: sha256=<hex>` over the raw body. Verify server-side before trusting payload.
- Retry policy
- Exponential backoff, 6 attempts over ~30 min. Persistent fails go to dead-letter — visible in dashboard.
- SSRF guard
- URL allowlist + RFC1918 / loopback / cloud-metadata block. Kill-switch `SSRF_GUARD=off`.
Webhooks
Create a webhook
FREEPOSTs the event JSON to your URL on every matching event. Returns a `secret` (one-time reveal) used to sign the `X-Sendora-Signature` header.
curl -X POST https://api.sendoracloud.com/api/v1/orgs/<ORG_UUID>/webhooks/endpoints \
-H "x-api-key: pk_prod_…" \
-d '{
"url": "https://your-app.com/hooks/sendora",
"events": ["email.delivered", "email.bounced", "auth.user_signed_up"]
}'
# Response includes "secret" — store it to verify signatures.Verify the signature
FREEHMAC-SHA256 of the raw request body using your webhook secret. Compare in constant time to defend against timing attacks.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody: string, signature: string, secret: string) {
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}Retry + backoff
FREEFailed deliveries (non-2xx, timeout, network error) retried in-process with exponential backoff over ~2 minutes (5 attempts including the initial fire). Final failure logged + visible in the Recent deliveries panel; Retry button on the dashboard re-fires a one-off attempt.
# No SDK call — automatic.
# Backoff schedule: initial fire, then 2s, 8s, 30s, 90s. Endpoints
# returning 2xx within ~2min won't see a final-failure log.Fire a test event
FREESend a synthetic event to verify your handler. Same signature flow as production events.
curl -X POST https://api.sendoracloud.com/api/v1/orgs/<ORG_UUID>/dev-tools/webhook-test \
-H "x-api-key: pk_prod_…" \
-d '{ "url": "https://your-app.com/hooks/sendora", "event": "email.delivered" }'