Core Concepts
Signature Verification
How HMAC signature verification works, what the Verified / Failed / Skipped stamps mean, and how to debug mismatches.
Your ingest URL is a public endpoint. Without verification, anyone who knows the URL can send fake events and trigger real actions in your application. Signature verification prevents this by confirming that each request was signed by your provider using a shared secret.
How it works
When a provider sends a webhook, it computes an HMAC-SHA256 hash of the raw request body using a secret key shared between you and them. That hash is included in a request header. On receipt, Watchtower computes the same hash using your configured secret and compares the two values using a timing-safe comparison.
If verification succeeds, the event is stamped Verified. A verification failure is stamped Failed; it is stored but forwarding is blocked only when Reject invalid signatures is enabled. With that setting off, failed events may be forwarded.
Verification stamps
Every event in your dashboard carries one of three stamps:
| Stamp | Meaning |
|---|---|
| Verified | Signature matched the configured secret and provider freshness checks passed. |
| Failed | Signature is missing, malformed, mismatched, or outside a provider timestamp tolerance. Quarantined only when Reject invalid signatures is enabled. |
| Skipped | Custom verification found no recognized signature header. Events without configured verification may have no stamp. |
Skipped is normal during initial setup, before you've added a signing secret. Once a secret is configured, all subsequent events are verified.
Provider formats
Each provider uses a slightly different signature format. The presets handle these automatically:
Stripe: signature is in the Stripe-Signature header as a comma-separated list of key=value pairs including a timestamp (t=) and one or more signatures (v1=). Watchtower constructs the signed payload as {timestamp}.{body} before verifying.
GitHub: signature is in the X-Hub-Signature-256 header as sha256={hex_digest}. Standard HMAC-SHA256 over the raw body.
Shopify: signature is in the X-Shopify-Hmac-SHA256 header as a base64-encoded HMAC-SHA256 digest.
Custom: recognized headers are x-signature, x-webhook-signature, x-hmac-sha256, and x-hub-signature. Verification accepts HMAC-SHA256 in hex or base64, with an optional sha256= or hmac= prefix.
Configuring verification at source creation
When you create a source and pick a provider preset (Stripe, GitHub, or Shopify), the Reject invalid signatures option is enabled by default. When verification returns Failed, the event is stored for inspection and acknowledged with 200, but no delivery is created. Custom requests with no recognized signature header return Skipped and are not quarantined by this flag. For Custom sources it's off by default, which is useful when you're still getting set up. You can change this setting any time from your source's settings page.
The signing secret can be left blank when creating a source and added later. Once a secret is saved, all subsequent events are verified against it. Events received before the secret was added have no verification stamp.
Authentication on forward and replay
Watchtower preserves raw body bytes and an explicit header allowlist: content-type; Stripe signature and event-type headers; GitHub signatures, delivery ID, event name, hook ID and installation-target metadata; Shopify HMAC, topic, shop domain, API version, webhook ID, event ID and triggered-at; the recognized custom signature headers and x-event-type; and Standard Webhooks / Svix signature, ID and timestamp headers. Preservation of a header does not imply native verification support for that provider.
Authorization, cookies, API keys, host, forwarding metadata, hop-by-hop headers (including fields named by Connection), and caller-supplied Watchtower authentication are excluded. Arbitrary custom headers are not forwarded. Forwarding uses POST.
Configured workspaces add a fresh x-watchtower-signature HMAC-SHA256 and x-watchtower-timestamp on every attempt, independently of provider headers. Verify timestamp.raw_body with your workspace signing secret and enforce freshness. If secret lookup or decryption fails, delivery fails closed and follows the retry policy. A legacy workspace with an explicitly unset secret remains unsigned until an owner initializes it in Settings → Security.
Preserving a provider signature does not guarantee delayed replay passes its freshness policy. Stripe's original timestamp can expire. Keep provider validation enabled on public provider endpoints; use an authenticated, idempotent historical-processing endpoint for intentional replay. Do not infer successful source verification merely because Watchtower forwarded the request: enable Reject invalid signatures and configure a supported verifier at ingest.
Debugging verification failures
Check the secret matches exactly. The most common cause of Failed stamps is a copied secret with trailing whitespace or a mismatch between test and live secrets. Go to your source settings, clear the secret field, and re-paste directly from your provider's dashboard.
Check for body modification. Verification is performed against the exact raw bytes received at the ingest URL. If a proxy, CDN, or load balancer in front of the ingest URL modifies the request body (re-encoding JSON, adding or removing whitespace), the hash won't match. This applies to any HMAC-based webhook verification, not just Watchtower.
Check the provider format. If you're using a Custom source for a provider that uses a non-standard format (Stripe-style timestamp prefix, base64 encoding, etc.), switch to the matching preset instead.
Missing signatures
Stripe, GitHub and Shopify verification returns Failed when a required signature is missing. Custom HMAC verification returns Skipped when none of its recognized signature headers is present. A source without a configured vendor and secret is not verified. Configure verification and the reject setting deliberately; do not treat Skipped or an absent stamp as trusted.
Need more help?
Check the FAQ for quick answers, or email hello@webhookwatchtower.co.uk . Include your event ID or workspace name to get a faster answer.