Skip to main content

Verify and Delivery

Request your endpoint receives​

POST /inbound HTTP/1.1
Content-Type: application/json; charset=utf-8
svix-id: 9f2c…(64 hex chars)
cm-message-id: [email protected]
svix-timestamp: 1791452467123
svix-signature: Zm9vYmFy…
HeaderDescription
svix-idLower-case hex SHA-256 identifying the message and recipient. Not a UUID. Stable across redeliveries, so use it as your idempotency key.
cm-message-idCanonicalised RFC Message-ID (no angle brackets).
svix-timestampUnix time in milliseconds (Svix proper uses seconds).
svix-signatureBase64 HMAC-SHA512, no v1, prefix.

Verify the signature​

signature = base64( HMAC_SHA512( key = UTF8(secret), data = svix-id + "." + svix-timestamp + "." + rawBodyBytes ) )
  • The key is your Webhook Secret (Settings → Integration) used as raw UTF-8. Do not base64-decode it or strip a whsec_ prefix.
  • Sign the exact bytes received. Do not parse and re-serialise.
  • This is the same scheme as outbound event webhooks, so one secret and one verifier cover both. A stock Svix library will reject it because of the millisecond timestamp.
const crypto = require('crypto');

function verify(secret, headers, rawBody /* Buffer */) {
const mac = crypto.createHmac('sha512', secret)
.update(`${headers['svix-id']}.${headers['svix-timestamp']}.`)
.update(rawBody)
.digest('base64');
const got = Buffer.from(headers['svix-signature'] || '');
const want = Buffer.from(mac);
return got.length === want.length && crypto.timingSafeEqual(got, want);
}

Also reject stale svix-timestamp values (for example older than 5 minutes) to limit replay.

Unsigned deliveries. If your account has no active webhook secret, requests are sent with only svix-id and cm-message-id. Create a secret to receive signed requests. If the secret cannot be read (transient error), the delivery is not made at all rather than sent unsigned.

Respond​

Return any 2xx status. CM.com waits up to 3 seconds to connect and 10 seconds in total. Respond quickly and process the message asynchronously.

Failures and replay​

Delivery is at-least-once. Deduplicate on svix-id.

  • A timeout, connection error, 408, 429 or 5xx is a transient failure. Any other non-2xx (including a 3xx) and a rejected URL is a permanent failure.
  • There is no automatic retry. Both kinds of failure hold the message for replay by CM.com. The message stays stored and visible in the Email App.
  • A replay only re-sends to the webhooks that did not succeed. A webhook that already returned 2xx for the message is skipped for 24 hours, so replays do not normally duplicate deliveries to it.