Skip to main content
Every webhook delivery carries an X-RadiumOne-Signature header. Verify it against the raw request body before you trust anything in the payload — an unverified webhook is just an HTTP POST from whoever can reach your endpoint.

Header anatomy

Two companion headers accompany every delivery: X-RadiumOne-Event-Id (same value as the payload’s id, useful for logging before you’ve parsed the body) and X-RadiumOne-Event-Type.

Algorithm

  1. Parse v, t, and s from the header.
  2. Build the signing string: `${v}.${t}.` (literal dot-joined v and t) concatenated with the raw request body bytes — not a re-serialized copy.
  3. Compute HMAC-SHA256(key, signing_string) and hex-encode it.
  4. Compare it to s using a constant-time comparison — never === or hmac == string, which can leak timing information about how many leading bytes matched.
  5. Reject if t is outside your tolerance window (300 seconds / 5 minutes is the reference implementation’s default) — this defends against replaying an old, otherwise-valid delivery.
Key encoding differs from the hosted-checkout redirect signature. The webhook signing key is the raw 32 bytes you get by stripping the whsec_ prefix and hex-decoding the remainder — never the display string itself. This is the opposite of the redirect signature, whose key is the full rsec_… string passed directly as the HMAC key (see Verify the payment result). Using the wrong encoding for either one makes every signature fail to verify.

Verify the signature

Read the raw body before any JSON parsing — most frameworks parse the request body automatically, which discards the exact bytes the signature was computed over. See Build your handler for the full handler order (raw body → verify → dedupe → enqueue → respond).

Known-answer test

RadiumOne’s own signer is tested against known-answer vectors, using the same dummy secret (never a real key). Run them against your own verifier to confirm you’ve implemented the algorithm correctly before going live. A representative subset: The valid case uses secret whsec_0000000000000000000000000000000000000000000000000000000000000000 and header v=1,t=1700000000,k=66687aad,s=e4cd0b87e9dd8eeb3390b2d4e7d35b07480bfab5a1d687bfe055fd7337a7b119. The full vector set — including tampered, expired, wrong-version, and malformed/forged cases — is tested against real Node.js, Python, PHP, and Java implementations of this algorithm before every release.

Tolerance and replay

  • A 5-minute (300 second) tolerance on t is the reference default — narrower windows increase the chance a slow network rejects a legitimate delivery; wider windows extend how long a captured signature stays replayable.
  • Dedupe on the event id regardless of your tolerance window — a retried delivery (see Retries, ordering, and duplicates) is a legitimate re-send with a fresh, valid signature, not a replay.

Rotating your secret

Rotating your webhook secret is a support-assisted operation today — see Register your endpoint. There’s no automatic overlap window on RadiumOne’s side: once your secret is rotated, the previous secret is irrecoverable. Deliveries already queued before the rotation, though, may still be retried using the old signature for as long as they keep retrying (up to the full backoff schedule, about 30 hours — see Retries, ordering, and duplicates). If you want those in-flight retries to keep verifying, keep your old secret available in your verifier for that same window after you rotate, then remove it.

Next steps

Webhooks overview

Handler steps: verify, dedupe, enqueue, respond.

Webhook event types

Every event and its payload shape.
Last modified on September 15, 2026