Skip to main content
Every webhook POST includes X-Sendmux-Signature-V2: v1=<hex> and X-Sendmux-Timestamp: <Unix seconds>. Verify the signature and timestamp before processing the event. The legacy X-Sendmux-Signature: sha256=<hex> header remains available.

What to sign

  • Use the webhook signing secret returned when the subscription was created or rotated.
  • Join X-Sendmux-Timestamp, a literal full stop (.), and the raw request body bytes.
  • Compute HMAC-SHA256 and compare it with v1 in constant time.
  • Reject timestamps outside your freshness window. The example accepts at most 300 seconds of clock difference in either direction.
Each retry has a fresh timestamp and signature. Its event ID and body stay unchanged. After verification, deduplicate by the signed body’s id; freshness checks do not prevent duplicates within the accepted window.
Do not parse and re-serialise the JSON before verification. That can change whitespace or field order and break the signature check.

Example verifier

Common mistakes

  • Reading parsed JSON instead of the raw body.
  • Verifying only the body instead of the timestamp and body together.
  • Accepting stale timestamps or keeping an inaccurate server clock.
  • Using a normal string comparison for secrets.
  • Processing retries without deduplicating the verified event ID.

Legacy verification

The legacy X-Sendmux-Signature value is sha256=<hex HMAC-SHA256> over the raw body bytes alone, using the same secret. It has no signed timestamp. Existing verifiers keep working; use V2 for freshness checks. A receiver configured for V2 should reject a missing or invalid V2 header instead of falling back to legacy verification.

Endpoint behaviour

Return a 2xx status once the event is accepted. For longer processing, store the event first, return quickly, and process it after the response. Sendmux retries non-2xx responses and timeouts. See Webhooks setup.