> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hookstream.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Signature Verification

Verify incoming webhooks with HMAC-SHA256, HMAC-SHA1, or Standard Webhooks.

Signatures prove a webhook came from the expected sender and not an attacker. The provider hashes the request body with a shared secret; hookstream recomputes the hash and compares with constant-time equality. Mismatch means 401.

Hookstream ships preconfigured signature verification for 15+ providers — Stripe, GitHub, Shopify, Clerk, Svix, Linear, and more. Use a [source template](/api/sources) and the secret is the only field you need to set.

## Supported algorithms

| Algorithm | `verification_type` | Used by |
| --- | --- | --- |
| HMAC-SHA256 | `hmac-sha256` | Stripe, GitHub, Shopify, Slack, most modern providers |
| HMAC-SHA1 | `hmac-sha1` | Legacy providers (pre-2020 GitHub, some CI systems) |
| Standard Webhooks | `standard-webhooks` | Svix, Clerk, Resend, and other Standard Webhooks adopters |

HMAC-SHA256 is the default recommendation. SHA-1 is still supported for legacy integrations but should not be chosen for new work.

## How each algorithm works

### HMAC-SHA256

The sender computes `HMAC-SHA256(secret, request_body)` and puts the hex digest in a header (for example `Stripe-Signature` or `X-Hub-Signature-256`). Hookstream:

1. Reads the signature header you configured on the source.
2. Recomputes the HMAC over the raw request body.
3. Compares using constant-time equality.

A common prefix like `sha256=` is stripped automatically before comparison.

### HMAC-SHA1

Same flow, but the hash function is SHA-1. Configure with `verification_type: "hmac-sha1"`.

### Standard Webhooks

[Standard Webhooks](https://www.standardwebhooks.com) is an open spec built on HMAC-SHA256 with a fixed set of headers:

- `webhook-id` — unique event id
- `webhook-timestamp` — Unix timestamp
- `webhook-signature` — `v1,<base64_signature>` (multiple versions allowed, comma separated)

The signed string is `webhook-id.webhook-timestamp.body` and the secret is base64-encoded. Configure with `verification_type: "standard-webhooks"`.

## Configure verification

**Get the signing secret from your provider**

In the provider's dashboard, create or view the webhook endpoint and copy the signing secret. For Stripe this is `whsec_...`; for GitHub it's whatever you typed into the "Secret" field when creating the hook.
  **Create a source with the secret**

Either use a template from `GET https://hookstream.io/v1/sources/templates` or post the fields directly:

    ```bash
    curl -X POST https://hookstream.io/v1/sources \
      -H "X-API-Key: $HOOKSTREAM_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Stripe Webhooks",
        "verification_type": "hmac-sha256",
        "verification_secret": "whsec_...",
        "verification_header": "stripe-signature"
      }'
    ```
  **Point your provider at the ingest URL**

Copy the URL from the source detail page — it looks like `https://hookstream.io/v1/ingest/src_<id>` — and paste it into the provider's webhook settings.
  **Verify with a real event**

Send a test event from the provider (or use the [Signature Validator](https://hookstream.io/tools/signature-validator) to simulate one). Successful requests land in your Events page with `verification_status: "verified"`.

Signature verification runs on the raw request body. If something upstream (a proxy, a transform) modifies the body before hookstream sees it, every signature will fail. Hookstream handles this correctly because it reads the body exactly once.

## Test and debug

Two free tools help when a signature keeps failing:

- [Signature Validator](https://hookstream.io/tools/signature-validator) — paste the secret, headers, and body to see step-by-step verification output.
- [HMAC Calculator](https://hookstream.io/tools/hmac-calculator) — compute an HMAC over arbitrary input to cross-check what your sender should be producing.

Events that fail verification are still stored (with `verification_status: "failed"`) so you can inspect the headers and body in the dashboard and figure out what went wrong.

## Next Steps

**[Sources API](/api/sources)**

Full source schema, including every verification field.
  **[IP Filtering](/guides/ip-filtering)**

Add a network-layer allowlist on top of signature verification.