How signing works
Each request carries three headers:
The signed payload is the concatenation:
Enable signing on a sink
- Store your signing key as an organization secret. Pick any random high-entropy value (32+ bytes recommended).
- Set
hash_keyon the webhook sink to the secret’s name.
Verify the signature
The verification recipe is the same in every language:- Read the three headers off the incoming request.
- Reject if
X-Webhook-Timestampis too old (e.g. more than 5 minutes ago) — this is your replay window. - Recompute
sha256=+hmac_sha256(key, nonce + "." + timestamp + "." + raw_body)in hex. - Compare against
X-Signature-256using a constant-time comparison.
If the sink has
enable_http_encoding set, Beam signs the compressed body — the same bytes that arrive on the wire with Content-Encoding: zstd. Run signature verification on the raw request body before decompressing it; hashing the decompressed payload will not match.- Python (FastAPI)
- Node.js (Express)
- Go (net/http)
Replay protection
The timestamp + nonce pair lets you reject duplicates:- Timestamp window — reject any request whose
X-Webhook-Timestampis more than ~5 minutes off your server clock. A short window keeps the nonce cache small. - Nonce cache (optional, defense-in-depth) — record each
X-Webhook-Nonceyou’ve accepted in a short-TTL cache (Redis, etc.) and reject repeats. Cache TTL should match your timestamp window.