Verify Webhook Signatures Before They Reach Your Network
Check HMAC signatures, JWTs and basic auth on Webhook Relay's edge, before a webhook is forwarded. Forged or tampered requests get a 401 and never reach your servers.
Most webhook endpoints check signatures in application code, after the request has already reached a server on your network. Webhook Relay can check them before forwarding. A forged, replayed or tampered request gets a 401 at the edge and never reaches your agent, your private network or your application.
How it works
- A provider sends a signed webhook to your bucket's public URL.
- A function attached to the input recomputes the signature with your secret and compares it with the header.
- If it matches, the webhook is forwarded, to a public URL or through an outbound-only agent into your private network.
- If it doesn't, the function sets a
401and stops forwarding. Nothing else happens, and the rejection is visible in the bucket logs.
local crypto = require('crypto')
-- Recompute the signature with the secret from the function's config variables
local sig, err = crypto.hmac('sha256', r.RequestBody, cfg:GetValue("WEBHOOK_SECRET"))
if err then error(err) end
-- GitHub sends "sha256=<hex>" in X-Hub-Signature-256
if "sha256=" .. sig ~= r.RequestHeader["X-Hub-Signature-256"] then
r:SetResponseStatusCode(401)
r:SetResponseBody("invalid signature")
r:StopForwarding()
return
end
For other providers, change the header name and the prefix. PagerDuty uses X-PagerDuty-Signature with v1=. Shopify sends a base64 digest in X-Shopify-Hmac-Sha256. The HMAC guide walks through creating the function, storing the secret and testing it.
Other ways to authenticate senders
| Method | Use it when | Guide |
|---|---|---|
| HMAC signature (function) | The provider signs the body: GitHub, Stripe, Shopify, Slack, PagerDuty, Square and most SaaS | HMAC |
| JWT | The sender puts a signed token in a header | JWT |
| Basic auth | The sender supports a username and password in the URL or header, like Grafana and Alertmanager | Username & password |
| HTTP method filter | Only POST should ever be accepted | HTTP method |
| Source IP allowlist | The provider publishes the IP ranges it sends from (Business and above) | IP allowlist |
Why verify at the edge
- Nothing unsigned enters your network. When webhooks are delivered through an outbound-only agent, the check happens before the agent ever sees the request.
- One place for every provider. The secret lives in the function's config variables, not spread across services.
- Visible rejections. Every request, accepted or rejected, is in the bucket logs, which helps when a provider rotates a secret.
- Your existing checks still work. Headers reach the destination unchanged.
Need to test a signature first? The free webhook signature verifier checks a payload, secret and header in your browser. For the bigger picture, see security solutions: SIEM alerts, GitOps and incident automation delivered without opening a port.
