Every Tallwatch webhook POST includes anDocumentation Index
Fetch the complete documentation index at: https://docs.tallwatch.com/llms.txt
Use this file to discover all available pages before exploring further.
X-Tallwatch-Signature header. Your receiver MUST verify this header against your signing secret before processing the body. Without verification, anyone who guesses your URL can forge alerts.
The contract
For every outbound request, Tallwatch computes:X-Tallwatch-Signature header.
To verify, your receiver re-computes the same HMAC over the raw body it received and compares it to the header value using a constant-time comparison. If they match, the request is genuine.
Verification snippets
Common verification mistakes
Verifying against the parsed body, not the raw bytes
Verifying against the parsed body, not the raw bytes
The signature is computed over the exact bytes Tallwatch sent. The moment you parse JSON and re-serialise it (
JSON.parse then JSON.stringify), whitespace and field order change. The HMAC will not match.Fix: read the raw body BEFORE any body-parser middleware runs. In Express, mount express.raw({ type: "application/json" }) ahead of express.json(). In Flask, call request.get_data() before request.get_json().Using `===` or `==` instead of constant-time comparison
Using `===` or `==` instead of constant-time comparison
A normal string equality check leaks timing information — a sophisticated attacker can guess your secret one byte at a time by measuring how long the comparison takes. Use the constant-time helper from your language’s crypto library:
- Node:
crypto.timingSafeEqual - Go:
hmac.Equal - Python:
hmac.compare_digest - Ruby:
Rack::Utils.secure_compareorOpenSSL.secure_compare
Forgetting the `sha256=` prefix
Forgetting the `sha256=` prefix
The header value starts with the literal
sha256= — six characters before the hex digest. If you compare just the hex part of the header against just the hex part of the expected value, you’re fine. If you compare the full header against just the hex, you fail. Most snippets above include the prefix on both sides.Using a UTF-8 string when the body is binary
Using a UTF-8 string when the body is binary
Tallwatch only sends JSON, so the body is always UTF-8 text. But your HMAC must run over the raw bytes received, not over a re-encoded string. In strongly-typed languages this usually means
Buffer/[]byte/bytes not string. In Python, request.get_data() returns bytes, which is what you want.Rotating the secret
The signing secret can be changed at any time from the channel form in Tallwatch. The change takes effect on the next dispatch. To rotate without downtime:Add the new secret to your receiver
Update your receiver’s config to accept signatures from EITHER the old or the new secret during the rollover window. Verify with both and accept either.
Update Tallwatch
Paste the new secret into the channel form and save. From this moment, all dispatches use the new secret.
Why HMAC-SHA256 and not JWT or mTLS
- HMAC-SHA256 is simple, fast, and supported by every language without dependencies. Verifying a JWT requires JWS libraries; mutual TLS requires cert provisioning on both sides.
- JWT would put the payload inside the token, which couples the body shape to the signing format and breaks the body-template feature.
- mTLS is more secure but operationally heavier — you’d need a cert per workspace and a renewal pipeline.