Chatonio

Verifying webhooks & reliability

July 7, 2026 ViewsChatonio API

Verifying webhooks

Every webhook request is signed so you can confirm it really came from Chatonio and wasn’t tampered with. Verify the signature before trusting the payload.

Request headers

Chatonio-Signature: t=1751366400,v1=5257a869...e3f0
Chatonio-Webhook-Id: d5e1a220-4f77-4c33-a1b2-c3d4e5f60718
Chatonio-Event: message.created
Chatonio-Delivery-Attempt: 1
Content-Type: application/json
  • Chatonio-Signaturet=<unix>,v1=<hex>. v1 is HMAC-SHA256(secret, "<t>.<raw_body>").
  • Chatonio-Webhook-Id — a unique id for this delivery; use it as an idempotency key so retries aren’t processed twice.
  • Chatonio-Event — the event type.
  • Chatonio-Delivery-Attempt — the attempt number.

How to verify

  1. Read the raw request body (bytes / string) — do not re-serialize the parsed JSON, or the signature won’t match.
  2. Parse t and v1 from the signature header.
  3. Compute HMAC-SHA256(signing_secret, t + "." + raw_body) and compare it to v1 with a constant-time comparison.
  4. Reject the request if the timestamp t is more than ~5 minutes from now (replay protection).
import crypto from 'node:crypto';

// Express example. Use the RAW request body, not a re-serialized object.
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(',').map((kv) => kv.split('='))
  );
  const t = Number(parts.t);
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');
  const ok = crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(parts.v1)
  );
  const fresh = Math.abs(Date.now() / 1000 - t) < 300; // 5-min replay window
  return ok && fresh;
}

Delivery & reliability

  • Retries — a failed delivery (timeout, connection error, 408, 429, or 5xx) is retried with exponential backoff, up to 5 times. Other 4xx responses are treated as permanent and not retried.
  • Idempotency — because of retries your endpoint may see the same event more than once; de-duplicate on Chatonio-Webhook-Id.
  • Auto-disable — after 15 consecutive failed deliveries the endpoint is disabled and the project owner is emailed. Re-enable it from the Developer page once your endpoint is healthy.
  • Delivery log — recent attempts (status, response code, errors) are visible per endpoint on the Developer page, retained for 30 days.
  • Ordering — delivery is not strictly ordered; rely on the payload’s created timestamps rather than arrival order.

Was this article helpful?