Skip to content
Developers & API

Webhooks

Webhooks push events to your backend the moment they happen — a message was delivered, a link was clicked, a contact unsubscribed. Every payload is signed so you can trust it, and failed deliveries are retried with backoff so a brief outage never loses an event.

7 min read

What you'll need

  • A public HTTPS endpoint to receive POSTs
  • An API key or admin access to register the webhook

Register an endpoint

  1. 1

    Open Settings → Webhooks

    Or call POST /v1/workspaces/{workspace_id}/webhooks. Provide your HTTPS URL and pick the events you want.

  2. 2

    Store the signing secret

    Each webhook has a secret, shown on creation. You'll use it to verify every incoming request.

  3. 3

    Add custom headers (optional)

    Attach headers (e.g. an auth token) that Climails will send with each delivery to your endpoint.

Events you can subscribe to

  • Email — email.delivered, email.bounce, email.complaint, email.unsubscribe, email.opened, email.clicked.
  • SMS — sms.delivered, sms.failed.
  • Contacts — contact.subscribed, contact.unsubscribed, contact.changed.
  • Campaigns — campaign.finished.

Verify the signature

Each request carries X-Senderbox-Timestamp and X-Senderbox-Sig. Recompute the HMAC and compare — reject anything that doesn't match, and reject old timestamps to stop replays.

X-Senderbox-Sig: sha256=<hex>
// signature = HMAC_SHA256(secret, timestamp + "." + rawBody)
import crypto from "node:crypto";

function verify(rawBody, headers, secret) {
  const ts  = headers["x-senderbox-timestamp"];
  const sig = headers["x-senderbox-sig"]; // "sha256=<hex>"
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(ts + "." + rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
Hash the raw request body exactly as received — don't re-serialize the JSON, or the signature won't match.

Retries and failures

Return a 2xx quickly to acknowledge. If your endpoint errors or times out, Climails retries with increasing backoff: 30s, 2m, 10m, 1h, 6h, 24h.

  • After 6 failed attempts the delivery is dropped.
  • A 4xx (except 408 and 429) is treated as terminal and not retried — fix the endpoint and it'll resume on the next event.
  • Endpoints are SSRF-protected: internal/private addresses are refused.

Frequently asked questions

How do I verify a webhook is really from Climails?

Recompute HMAC-SHA256 over timestamp + "." + raw body with the webhook's secret and compare to X-Senderbox-Sig. Use a constant-time compare.

What happens if my server is down?

Deliveries retry with backoff (30s → 24h) across up to 6 attempts, so a short outage won't lose events.

Why was my webhook stopped retrying immediately?

A 4xx response (other than 408/429) is treated as a permanent error. Fix the handler; new events resume delivery.

Start sending in minutes

Create a free account, connect your domain and reach your audience across every channel — no credit card needed.

Free plan forever · No credit card required · Set up in minutes

Webhooks: delivery, engagement & contact events with signatures — Climails