Skip to main content
Signal signs every webhook delivery with the endpoint secret. Verify the signature before treating any payload field as trusted.

Headers and signed input

The signed input is the timestamp, one literal period, and the exact raw request body:
Signal calculates HMAC-SHA256 with the endpoint secret.

Verification sequence

1

Preserve the raw body

Read the bytes or exact string before a framework parses and serializes JSON.
2

Require both headers

Reject a request with a missing or malformed timestamp or signature.
3

Check freshness

Parse epoch seconds and apply your receiver’s replay window with a small clock-skew allowance.
4

Calculate HMAC

Sign timestamp + "." + rawBody with HMAC-SHA256 and format the hex result with the v1= prefix.
5

Compare safely

Compare equal-length byte sequences with a constant-time comparison.
6

Parse and accept

Only after verification, parse JSON, deduplicate, and durably store or enqueue the event.

Secret lifecycle

The create-webhook response returns the signing secret once. List and get responses never return it. Store one endpoint secret in a server-side secret manager and redact it from logs and exceptions. When rotating a secret through an endpoint update, coordinate receiver deployment so the accepted-secret window is explicit.

Common verification failures

  • JSON was parsed and serialized before HMAC calculation.
  • A newline or character encoding changed.
  • The wrong endpoint secret was selected.
  • The v1= prefix was omitted during comparison.
  • Seconds were interpreted as milliseconds.
  • A reverse proxy consumed or modified the request body.
Equivalent JSON objects do not have equivalent signatures if their raw bytes differ. Use the body exactly as received.

Implementation example

See a Node.js constant-time verification example.