X-Tallwatch-Signature header. Verify it against your signing secret before you process the body. Skip that, and anyone who learns your URL can forge alerts.
The scheme
The header follows Stripe’s format:t is the Unix timestamp at signing time (also sent as X-Tallwatch-Timestamp). v1 is the signature. Tallwatch computes it as:
t=, the timestamp, a ., then the raw request body. To verify, parse t and v1 out of the header, recompute the HMAC over the same string, and compare it to v1 with a constant-time check.
Verification snippets
Common mistakes
Signing the body alone, without the `t=<ts>.` prefix
Signing the body alone, without the `t=<ts>.` prefix
The signed string is
t=<ts>.<body>, not the body on its own. Prepend the literal t=, the timestamp from the header, and a . before the raw body, or the HMAC won’t match.Comparing the whole header instead of just `v1`
Comparing the whole header instead of just `v1`
The header is
t=...,v1=.... Compare your computed HMAC against the v1 value, not the entire header string.Verifying the parsed body, not the raw bytes
Verifying the parsed body, not the raw bytes
Parsing JSON and re-serializing it changes whitespace and field order, so the HMAC breaks. Read the raw body before any body-parser runs. In Express, mount
express.raw() ahead of express.json(). In Flask, call request.get_data() before request.get_json().Using `==` instead of a constant-time comparison
Using `==` instead of a constant-time comparison
A plain equality check leaks timing, which lets a determined attacker recover the signature byte by byte. Use your language’s constant-time helper:
- Node:
crypto.timingSafeEqual - Go:
hmac.Equal - Python:
hmac.compare_digest - Ruby:
Rack::Utils.secure_compare
Guard against replays
The timestamp is signed, so it can’t be tampered with after the fact. Reject any request whoset is more than a few minutes from your clock (300 seconds is a reasonable window). That way a captured request can’t be replayed against you later. The Node snippet above shows the check.
Rotate the secret
Change the signing secret any time from the channel form. The change takes effect on the next dispatch. To rotate without dropping alerts:1
Generate a new secret
Run
openssl rand -base64 32 or equivalent.2
Accept both secrets in your receiver
Verify against the old and the new secret, accepting either, for the rollover window.
3
Update Tallwatch
Paste the new secret into the channel form and save. Every dispatch now uses it.
4
Drop the old secret
Once your logs show traffic verifying under the new secret, remove the old one.