For the complete documentation index, see llms.txt. This page is also available as Markdown.

Webhook Signing

Outbound webhooks from Revenium can be cryptographically signed so receivers verify the payload originated from Revenium and was not tampered with in transit. Signing is opt-in per webhook: enable it from the dashboard, copy the secret once, verify every delivery against that secret.

How it works

When a webhook has signing enabled, every dispatch carries two headers and a deterministic signature over the request body. The receiver recomputes the signature locally and compares against the one on the wire. If they match, the payload is authentic.

Webhooks without signing enabled keep being delivered unchanged. No headers are added, and the receiver does not need to do anything different.

Headers

Two headers appear on every signed delivery.

Header
Meaning

X-Revenium-Signature-256

sha256=<hex>. During a 24h rotation overlap, two signatures are present: sha256=<hex_new>, sha256=<hex_previous> (RFC 7230 multi-value, comma + space).

X-Revenium-Webhook-Timestamp

Unix epoch seconds at signing time. Used both as part of the signed payload and to reject replays.

Verification algorithm

signed_payload = <X-Revenium-Webhook-Timestamp> + "." + raw_request_body_bytes
expected       = HMAC-SHA256(secret, signed_payload)
header_value   = "sha256=" + lowercase_hex(expected)

Three rules a correct verifier must follow:

  1. Sign the raw wire bytes, not a re-serialised copy. If your framework parses JSON then stringifies it back, whitespace and key order differ from what was signed. Read the body as bytes before JSON-decoding.

  2. Reject the request if the timestamp drifts more than 300 seconds from your local clock. This is the replay defence.

  3. Use a constant-time comparison. Python: hmac.compare_digest. Node: crypto.timingSafeEqual. Go: hmac.Equal. Direct == comparison leaks the signature through timing side-channels.

Multi-signature during rotation

When a customer rotates the signing secret in the dashboard, the previous secret remains valid for 24 hours so receivers can roll forward without downtime. During that window, every delivery is signed with both secrets, and the header carries both signatures comma-separated:

Receivers verify by trying their currently-deployed secret against every signature in the header. As long as one matches, the delivery is authentic. After the 24h window expires, the header returns to a single signature on the new secret.

An immediate rotation (used when a secret is known to be compromised) skips the overlap window: the previous secret stops verifying right away.

Testing your receiver

The API exposes two endpoints that together let you produce a signed delivery on demand, useful during integration:

  1. Enable signing or rotate the secret. POST /profitstream/v2/api/export-configurations/{id}/rotate-signing-secret?teamId=... returns { "signingSecret": "<value>" }. The secret is shown exactly once. Body { "immediate": true } skips the rotation overlap; {} (default) starts the 24h graceful window.

  2. Trigger a synthetic delivery. POST /profitstream/v2/api/export-configurations/{id}/send-test-event?teamId=... dispatches a synthetic event through the same code path used in production. The receiver sees the same headers and signing behaviour as a real event.

Configure your receiver to log the headers and body, capture one synthetic delivery, run it through your verifier, and confirm the signature matches.

Code examples

Reference implementations using language standard libraries.

Python

Node

Go

  1. Read the body as bytes before parsing. Frameworks that auto-decode JSON may discard the original byte stream. If you can only get the parsed object, you cannot verify.

  2. Verify before any side effect. Reject unverified deliveries early; do not write to your database or trigger downstream calls until the signature checks out.

  3. Keep both secrets while you rotate. During the 24h overlap, your receiver should hold the new secret and the previous one. Drop the previous secret only after the window closes.

  4. Persist a small dedup window. Two retries can deliver the same event; the signature confirms authenticity but not uniqueness. Use the event identifier in the payload (or the timestamp + body hash) to deduplicate on your side.

If you have a use case that needs broader signing coverage or alternative algorithms, contact support.

Last updated

Was this helpful?