# Inbound verification

How Webhooker verifies a webhook signature before accepting the payload — Stripe, GitHub, Shopify, custom HMAC-SHA256, basic auth and API key — and what a failed check does.

Source: https://docs.webhooker.eu/receive/verification/

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 source](https://docs.webhooker.eu/screenshots/source-verification-light.webp "Verification lives on the source's Settings tab. Choosing a preset fills in the header, algorithm and encoding.")

## Supported schemes

| Method | Header read | Algorithm | Encoding | Timestamp tolerance |
| --- | --- | --- | --- | --- |
| Stripe | `Stripe-Signature` | HMAC-SHA256 | hex | 5 minutes |
| GitHub | `X-Hub-Signature-256` | HMAC-SHA256 | hex (`sha256=` prefix) | none |
| Shopify | `X-Shopify-Hmac-Sha256` | HMAC-SHA256 | base64 | none |
| HMAC | the header you name | SHA-256 or SHA-1 | hex or base64 | 5 minutes when a timestamp header is set |
| Basic Auth | `Authorization` | — | — | — |
| API Key | the 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](https://webhooker.eu/blog/verify-stripe-webhook-signatures),
[GitHub's](https://webhooker.eu/blog/verify-github-webhook-signature) and
[Shopify's](https://webhooker.eu/blog/shopify-webhook-verification-gdpr)
signatures.

## Custom senders

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

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.

## What happens on failure

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](https://webhooker.eu/blog/webhook-signature-verification-failed).

Each event's verification result is shown as:

| Status | Meaning |
| --- | --- |
| `verified` | The signature or credential matched. |
| `failed` | It did not. Rejected with `401`. |
| `skipped` | Verification is off for this source. |

## Replay protection

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](https://webhooker.eu/blog/webhook-replay-attacks-timestamp-tolerance)
explains what an attacker can do when a scheme signs no timestamp at all.
