# Verify incoming webhooks

Check every request's authenticity before it is stored or delivered.

Anyone who learns a source URL can post to it. Verification proves a request came from the sender you expect, before Ensue stores it or creates an event. A request that fails is refused with `401`, logged with the reason, and never delivered.

## Verification methods

Set the method with `auth_type` when you create or update a source.

| `auth_type` | Use it when the sender… |
|---|---|
| `none` | doesn't sign requests. Fine for testing, not for production. |
| `stripe` | is Stripe. Checks the `Stripe-Signature` header (`t=…,v1=…`) with the endpoint's `whsec_` secret. |
| `hmac` | signs the body with a shared secret in a header, as GitHub does. |
| `hmac_ts` | signs a timestamp plus the body, with a replay window. |
| `standard` | follows the [Standard Webhooks](https://www.standardwebhooks.com) scheme (`webhook-id`, `webhook-timestamp`, `webhook-signature`). Svix senders, which use the same scheme under `svix-*` header names, work too. |
| `bearer` | sends `Authorization: Bearer <token>`. |
| `basic` | sends a username and password. |
| `apikey` | sends a fixed key in a header you name. |

## Set it up

1. Open the source and choose **Verification**.
2. Select the method and enter the secret or token the sender uses.
3. Save, then trigger a test event from the sender.

Over the API:

```bash
curl -X POST https://api.getensue.com/v1/sources \
  -H "authorization: Bearer ens_<key>" -H "content-type: application/json" \
  -d '{
    "name": "github-main",
    "auth_type": "hmac",
    "header": "x-hub-signature-256",
    "prefix": "sha256=",
    "encoding": "hex",
    "secret": "<shared secret>"
  }'
```

### HMAC options

| Field | Meaning |
|---|---|
| `header` | The header that carries the signature. |
| `prefix` | Text before the signature in the header, such as `sha256=`. |
| `encoding` | `hex` (default) or `base64`. |

### Standard Webhooks options

`tolerance` sets how many seconds the timestamp may differ from Ensue's clock, from 0 to 3600. The default is 300. A request outside the window is refused with a message naming the tolerance, which stops an attacker replaying a captured request later.

## Check what happened

A refused request shows in the source's log with its reason, for example a missing header or a signature mismatch. The log keeps the request's headers and body so you can compare it against what the sender documents.

> [!WARNING]
> A source set to `none` accepts anything sent to its URL. Treat the URL as a secret and switch to a real method before you go live.

## Rotating secrets

Update the secret in Ensue and at the sender at the same time. Requests signed with the old secret are refused as soon as you save. If the sender supports two active secrets, add the new one at the sender first, update Ensue, then remove the old one.

## Verifying Ensue's own requests

Verification works in both directions. Ensue signs what it delivers to your destinations with `x-ensue-signature`; see [Destinations](/gateway/destinations/#what-your-service-receives). To verify in your service, compute the HMAC-SHA256 of `"<timestamp>.<raw body>"` with the destination's secret, compare it in constant time with the `v1` value, and reject old timestamps.