Verification
Anyone who learns a source’s ingress URL can send requests to it. Verification checks each request against the secret the provider signs with, and refuses what does not match before anything is stored.
Verification modes
Section titled “Verification modes”verificationMode |
What is checked |
|---|---|
NONE |
Nothing. Whatever reaches the URL is stored and forwarded. Use it only while you are wiring a provider up. |
PROVIDER |
The provider’s own scheme, chosen by providerType. Every provider type except GENERIC has one. |
HMAC_GENERIC |
An HMAC-SHA256 signature in a header you name. This is how a GENERIC provider is verified. |
A request that fails verification is answered 401 and is not stored.
Providers
Section titled “Providers”providerType |
Header checked | How |
|---|---|---|
GITHUB |
X-Hub-Signature-256 |
sha256= followed by the hex HMAC-SHA256 of the raw body. |
GITLAB |
X-Gitlab-Token |
The header must equal the secret. GitLab sends a token, not a signature. |
STRIPE |
Stripe-Signature |
t=…,v1=…: hex HMAC-SHA256 of <t>.<raw body>. t must be within 300 seconds of now. |
SHOPIFY |
X-Shopify-Hmac-SHA256 |
Base64 HMAC-SHA256 of the raw body. |
SLACK |
X-Slack-Signature, X-Slack-Request-Timestamp |
v0= followed by the hex HMAC-SHA256 of v0:<timestamp>:<raw body>. The timestamp must be within 300 seconds of now. |
TWILIO |
X-Twilio-Signature |
Base64 HMAC-SHA1 with the auth token. For form-encoded requests it covers the URL followed by every parameter sorted by name; for other requests it covers the URL, and the bodySHA256 query parameter must match the body. |
For every provider the secret goes in the source’s hmacSecret: the webhook signing secret, the GitLab secret token, or the Twilio auth token.
Set up a provider
Section titled “Set up a provider”-
Create the source with
verificationModeset toPROVIDER, theproviderType, and the provider’s secret:Terminal window curl -X POST "$RAILHOOK_URL/api/v1/projects/$PROJECT_ID/incoming-sources" \-H "X-API-Key: $API_KEY" \-H "Content-Type: application/json" \-d '{"name":"GitHub","providerType":"GITHUB","verificationMode":"PROVIDER","hmacSecret":"'"$GITHUB_WEBHOOK_SECRET"'"}' -
Paste the source’s
ingressUrlinto the provider’s webhook settings, with the same secret. -
Send a test webhook from the provider. It appears under Received; a signature mismatch is answered
401and does not.
Generic HMAC
Section titled “Generic HMAC”For a provider without a preset, use HMAC_GENERIC:
| Field | Default | Meaning |
|---|---|---|
hmacHeaderName |
X-Signature |
The header that carries the signature. |
hmacSignaturePrefix |
none | A prefix to strip before comparing, such as sha256=. |
hmacSecret |
The shared secret. |
Two header shapes are accepted:
| Shape | Signed content | Replay protection |
|---|---|---|
t=<unix-ms>,v1=<hex>, Railhook’s own format |
<t>.<raw body> |
t must be within 300 seconds of now. |
| A bare hex digest | The raw body only | None from the signature: the same request verifies again for as long as the secret lives. |
curl -X POST "$RAILHOOK_URL/api/v1/projects/$PROJECT_ID/incoming-sources" \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Acme","providerType":"GENERIC","verificationMode":"HMAC_GENERIC","hmacHeaderName":"X-Acme-Signature","hmacSignaturePrefix":"sha256=","hmacSecret":"'"$ACME_SECRET"'"}'Replayed signatures
Section titled “Replayed signatures”A verified signature is remembered for a window, and a second request with the same signature inside that window is refused with 401. For providers whose signature carries a timestamp this suppresses duplicates. For a bare hex digest it is the only protection against a captured request being sent again.
| Variable | Default |
|---|---|
WEBHOOK_INGRESS_REPLAY_WINDOW_MINUTES |
5 |