Skip to content

Verification answers one question: did this request really come from the sender you expect? It runs before anything is stored, and it is configured per source under Settings → Authenticate inbound requests.

Leave it off and every request is accepted and recorded as skipped. That is fine while you are exploring, and wrong for production: the ingest URL is public, so anyone who learns it can post to your source.

The Authenticate card of a sourceThe Authenticate card of a source
Verification lives on the source's Settings tab. Choosing a preset fills in the header, algorithm and encoding.
MethodHeader readAlgorithmEncodingTimestamp tolerance
StripeStripe-SignatureHMAC-SHA256hex5 minutes
GitHubX-Hub-Signature-256HMAC-SHA256hex (sha256= prefix)none
ShopifyX-Shopify-Hmac-Sha256HMAC-SHA256base64none
HMACthe header you nameSHA-256 or SHA-1hex or base645 minutes when a timestamp header is set
Basic AuthAuthorization
API Keythe header you name

The three presets need nothing but the provider’s secret; the header name, algorithm, encoding and timestamp handling are already correct for that provider. If you also verify the same signature yourself, the scheme behind each preset is written out in the guides to Stripe’s, GitHub’s and Shopify’s signatures.

Pick HMAC for any other signed sender and fill in what it does:

  • Signature header — where the signature arrives, for example X-Signature.
  • Timestamp header — optional. Set it when the sender signs {timestamp}.{body} and you want replayed requests rejected. Without it, only the body is signed and no freshness check applies.
  • Algorithm — SHA-256 or SHA-1.
  • Encoding — hex or base64.

For senders that authenticate rather than sign, Basic Auth compares a username and password against the Authorization header, and API Key compares a fixed value against a header you name. Both are weaker than a signature — they prove the sender knows a secret, not that this particular body is untouched — but they are far better than nothing.

Secrets are stored encrypted and never returned to the browser. A saved field reads “Re-enter secret to change”; submitting that placeholder is rejected, so type the new secret in full when you rotate it.

A request whose signature does not verify is answered 401 and still stored, marked failed, with no deliveries created. Nothing reaches your app, and the attempt is visible in the event log with all its headers — which is how you tell a wrong secret from a wrong URL.

Two consequences worth knowing:

  • Failed events count against your monthly quota and your retention window.
  • A misconfigured secret is silent on the provider’s side except for the 401. If a provider reports failing webhooks, check the event log first. The usual causes — a re-serialized body, the wrong secret for the environment, a trimmed header — are collected in why webhook signature verification fails.

Each event’s verification result is shown as:

StatusMeaning
verifiedThe signature or credential matched.
failedIt did not. Rejected with 401.
skippedVerification is off for this source.

Where a timestamp is part of the scheme — Stripe, or HMAC with a timestamp header — a request whose timestamp is more than 5 minutes from the server clock fails verification even if the signature is otherwise correct. That is what stops a captured request from being replayed later, and why the tolerance window matters explains what an attacker can do when a scheme signs no timestamp at all.