Skip to main content

Webhook signing

Every webhook delivery is signed with HMAC-SHA256 using your endpoint’s signing secret. Verify the signature before parsing the payload into business logic, and keep the raw request body bytes. Verification depends on the exact payload sent, so re-serializing parsed JSON will produce a different digest.

Signature headers

X-Cope-Signature is a structured value, not a bare digest. Parse out the t and v1 components before comparing. Comparing the raw header value against a computed digest always fails.

Verification steps

  1. Read the raw request body bytes before any JSON parsing.
  2. Parse t (timestamp) and v1 (hex signature) from X-Cope-Signature.
  3. Reject the delivery if t is outside your tolerance window. COPE recommends 5 minutes to guard against replay.
  4. Compute HMAC_SHA256(signing_secret, "{t}.{raw_body}") and hex-encode it.
  5. Compare against v1 with a constant-time comparison.

Worked example

For a delivery with:
and raw body {"id":"evt_123",...}, the expected signature is:
That value must equal the v1 component, 5f6e...9c1d. Copy-paste verification snippets for Node, Python, PHP, Ruby, and Go are shown in the dashboard when an endpoint is created.

Common verification mistakes

  • Comparing the raw X-Cope-Signature value against the digest. Extract v1= first.
  • Signing only the body. The signed string is "{timestamp}.{body}", joined with a literal ..
  • Re-serializing the body. Parse-then-stringify changes key order, whitespace, or Unicode escaping; always HMAC the raw bytes as received.
  • Using the wrong encoding. The signature is lowercase hex, not base64, and carries no sha256= prefix.
  • Using the wrong secret. Secrets are per endpoint and shown only when the endpoint is created; a recreated endpoint gets a new secret and the old one stops verifying.
4xx responses from your endpoint are treated as terminal. The delivery is recorded as failed and is not retried automatically. Fix verification, then replay the delivery from the webhook details view in the COPE dashboard.

Consumer checklist

  • Read the raw request body before JSON parsing.
  • Verify the signature header with the endpoint secret configured for the subscription.
  • Reject missing, malformed, or stale signatures.
  • Deduplicate only after verification.
  • Return 2xx only after durable acceptance.
Endpoint signing secrets are shown only when an endpoint is created. Store the value immediately — if you need a replacement secret, recreate the endpoint in the COPE dashboard.