Guide

Webhook Signature Validation Explained

Webhook signature validation answers two separate questions: did the inbound request really come from the provider, and did the outbound delivery really come from FastHook?

FastHook supports source-level verification for inbound requests with BASIC_AUTH, API_KEY, and HMAC. FastHook also signs outbound HTTP destination deliveries with the project signing secret when the destination uses FASTHOOK_SIGNATURE.

This guide explains both directions, the exact headers and payloads involved, how the dashboard marks requests as verified, and how to debug rejected or unsigned traffic.

Two Verification Directions

Inbound verification happens on a Source. It protects FastHook from accepting forged provider requests. If a source has auth_type set and the request passes that check, the stored request is marked verified.

Outbound verification happens at your Destination. It protects your receiver from accepting requests that did not come through FastHook. FastHook signs the delivery payload and your app verifies the timestamp and signature before trusting the body.

  • Source auth: provider or test client -> FastHook source.
  • Destination signature: FastHook -> your HTTP destination.
  • Request verified flag: true only when source auth is configured and passes.
  • No source auth: FastHook can still accept the request, but it appears as unverified.
  • A rejected source auth request is stored as rejected with SOURCE_AUTH_FAILED.
The Requests verified flag describes inbound source auth only; outbound signatures are verified by your HTTP receiver.

Inbound Source Verification

Configure inbound verification on the source config. FastHook checks source state and allowed HTTP method first, then verifies auth. If auth fails, FastHook rejects the request before delivery and stores the request with SOURCE_AUTH_FAILED.

Source auth is intentionally source-level. Connections, filters, transformations, retries, and destinations only see accepted source requests.

Source config
{
  "config": {
    "auth_type": "API_KEY",
    "auth": {
      "header_name": "x-provider-token",
      "value": "secret-token-from-provider"
    },
    "allowed_http_methods": ["POST"]
  }
}

Source Auth Modes

FastHook accepts three inbound source auth modes. Use the mode that matches the provider or the test client sending to the source URL.

In the dashboard, open a source, enable Authenticate, then choose HMAC, Basic Auth, or API key. The dashboard stores those settings as config.auth_type and config.auth on the source.

  • BASIC_AUTH reads the Authorization header, decodes Basic credentials, and compares username and password.
  • API_KEY reads a configured header, defaulting to x-api-key, and compares it with the configured value.
  • HMAC computes an HMAC-SHA256 hex digest from the raw request body, or timestamp.rawBody when a timestamp header is configured and present.
  • HMAC accepts an optional prefix, such as sha256=, when a provider includes a scheme before the hex digest.
  • Unknown or empty auth_type is treated as no source auth.
Source authentication is configured on the Source and runs before connection routing or destination delivery.

Basic Auth Source

Use BASIC_AUTH when the provider can send an Authorization: Basic header. FastHook compares both username and password with constant-time text comparison.

Basic auth
{
  "config": {
    "auth_type": "BASIC_AUTH",
    "auth": {
      "username": "provider-user",
      "password": "provider-password"
    }
  }
}

curl.exe -X POST "https://hook-xxxxxx.fasthook.io/" -u provider-user:provider-password -H "content-type: application/json" -d "{""type"":""invoice.created""}"

API Key Source

Use API_KEY when the sender can provide a shared token in a header. If header_name is omitted, FastHook reads x-api-key.

API key
{
  "config": {
    "auth_type": "API_KEY",
    "auth": {
      "header_name": "x-provider-token",
      "value": "provider-secret-token"
    }
  }
}

curl.exe -X POST "https://hook-xxxxxx.fasthook.io/" -H "x-provider-token: provider-secret-token" -H "content-type: application/json" -d "{""type"":""invoice.created""}"

HMAC Source

Use HMAC when the provider signs the request body. FastHook reads the raw body text, computes HMAC-SHA256 with the configured secret, and compares the hex digest with the configured signature header.

By default, FastHook reads x-fasthook-signature and expects only the hex digest. Set signature_header for provider-specific names such as stripe-signature or x-hub-signature-256. Set prefix when the header value includes a prefix such as sha256=.

If timestamp_header is configured and that header is present on the request, FastHook signs timestamp.rawBody. Source HMAC validation does not apply a timestamp tolerance check by itself; use provider replay controls or destination-side timestamp checks where needed.

FastHook source HMAC compares the provider signature against an HMAC-SHA256 digest of the exact raw request body.
HMAC config
{
  "config": {
    "auth_type": "HMAC",
    "auth": {
      "secret": "whsec_provider_secret",
      "signature_header": "x-provider-signature",
      "timestamp_header": "x-provider-timestamp",
      "prefix": "sha256="
    }
  }
}

Send A Test HMAC Request

When testing HMAC locally, sign the exact body string that curl sends. Changing whitespace, JSON key order, or line endings changes the digest.

HMAC test
node -e "const crypto=require('node:crypto'); const body=JSON.stringify({type:'invoice.created',id:'evt_test_123'}); const timestamp=Math.floor(Date.now()/1000).toString(); const digest=crypto.createHmac('sha256','whsec_provider_secret').update(timestamp+'.'+body).digest('hex'); console.log('body='+body); console.log('timestamp='+timestamp); console.log('signature=sha256='+digest);"

$BODY = "{""type"":""invoice.created"",""id"":""evt_test_123""}"
$TIMESTAMP = "1772131200"
$SIGNATURE = "sha256=<digest-from-the-same-body-and-timestamp>"

curl.exe -X POST "https://hook-xxxxxx.fasthook.io/" -H "content-type: application/json" -H "x-provider-timestamp: $TIMESTAMP" -H "x-provider-signature: $SIGNATURE" -d $BODY

What Verified Means

The Requests table and API verified field describe source verification, not destination verification. A request is verified when the source has auth_type configured and the inbound check passes.

If a source has no auth_type, a valid request can still be accepted and routed, but the request is shown as unverified. That is expected and does not mean delivery failed.

  • verified=true: source auth was configured and passed.
  • verified=false with accepted status: source auth was not configured.
  • rejected with SOURCE_AUTH_FAILED: source auth was configured and failed.
  • Source Method Not Allowed is checked before auth and means the request used a method not in allowed_http_methods.
  • Use the Requests detail panel to inspect the stored headers and rejection cause.

Outbound FastHook Signatures

When an HTTP destination uses FASTHOOK_SIGNATURE, FastHook signs each delivery with the project signing secret. The signature covers the exact outbound payload text after FastHook routing and transformations.

FastHook adds x-fasthook-timestamp and x-fasthook-signature to the delivery request. The signature format is v1=<hex hmac sha256>. The signed value is timestamp.rawBody.

Use the project signing_secret to verify FastHook outbound HTTP deliveries before parsing the request body.
Delivery headers
x-fasthook-timestamp: 1772131200
x-fasthook-signature: v1=0f4d...
x-fasthook-event-id: evt_...
x-fasthook-request-id: req_...
x-fasthook-connection-id: con_...
x-fasthook-event-data-id: evd_...

Destination Config For Signed Delivery

Destination delivery signatures are a destination concern. For HTTP destinations, set config.auth_type to FASTHOOK_SIGNATURE when you want FastHook to add the signature headers. CLI destinations are local tunnel deliveries and do not use these outbound signature headers.

The signing secret lives in Project Secrets as signing_secret.value. It is different from api_key.value, which authenticates Control API calls and fasthook-cli.

Destination config
{
  "name": "billing-api",
  "type": "HTTP",
  "config": {
    "url": "https://api.example.com/webhooks",
    "http_method": "POST",
    "auth_type": "FASTHOOK_SIGNATURE",
    "auth": {}
  }
}

Verify FastHook In Your App

Your receiver should verify the FastHook signature before parsing or trusting the payload. Use the raw request body, not a re-serialized JSON object. Reject missing timestamps, stale timestamps, malformed signatures, and mismatches.

The default helper in the backend uses a 5 minute tolerance for FastHook signatures. The sample below follows the same rule.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

function safeEqual(left, right) {
  const leftBuffer = Buffer.from(left);
  const rightBuffer = Buffer.from(right);
  if (leftBuffer.length !== rightBuffer.length) return false;
  return timingSafeEqual(leftBuffer, rightBuffer);
}

export function verifyFastHookDelivery({ rawBody, timestamp, signature, signingSecret }) {
  if (!signingSecret) return false;
  if (!timestamp || !/^\d+$/.test(timestamp)) return false;

  const timestampSeconds = Number(timestamp);
  const nowSeconds = Math.floor(Date.now() / 1000);
  if (!Number.isSafeInteger(timestampSeconds)) return false;
  if (Math.abs(nowSeconds - timestampSeconds) > 300) return false;

  if (!signature || !signature.startsWith("v1=")) return false;
  const digest = createHmac("sha256", signingSecret).update(timestamp + "." + rawBody).digest("hex");
  return safeEqual("v1=" + digest, signature);
}

Raw Body Rules

Both provider HMAC verification and FastHook delivery signature verification depend on the raw body string. Many frameworks parse JSON automatically, which can lose the exact bytes needed for signature verification.

  • Read the raw body before JSON parsing.
  • Do not pretty-print, trim, normalize line endings, or re-stringify JSON before verifying.
  • Use the same character encoding that arrived over HTTP.
  • Verify first, parse second.
  • For destination verification, verify the transformed outbound payload, not the original provider body.

Rotate Signing Secrets

Project Secrets contains the API key used by the control API and CLI tunnel, and the signing secret used for outbound FastHook delivery signatures. They are different secrets.

Rotate the signing secret if it was exposed in logs, screenshots, source code, or chat. After rotation, update every receiver that verifies FastHook deliveries.

  • API Key: used by Admin API requests and fasthook-cli tunnel login.
  • Signing Secret: used to verify FastHook outbound deliveries.
  • Source HMAC secrets: provider-specific secrets stored in source config.
  • Provider secrets and FastHook signing secrets should not be reused.

Troubleshooting Source Auth

  • missing_basic_auth: Authorization header is missing or is not Basic.
  • invalid_basic_auth: username or password does not match the source config.
  • missing_api_key: configured API key header is missing.
  • invalid_api_key: API key header is present but the value does not match.
  • missing_hmac_signature: configured signature header is missing.
  • invalid_hmac_signature: digest mismatch, wrong secret, wrong raw body, wrong prefix, or wrong timestamp input.
  • SOURCE_METHOD_NOT_ALLOWED: method was rejected before auth because allowed_http_methods does not include it.
  • accepted but unverified: source auth_type is null, so FastHook accepted the request without source verification.

Troubleshooting Delivery Signatures

  • missing timestamp: x-fasthook-timestamp was not present on the destination request.
  • timestamp outside tolerance: receiver clock is wrong, request was delayed too long, or the request is a replay.
  • invalid signature format: x-fasthook-signature does not start with v1=.
  • signature mismatch: wrong signing secret, parsed body instead of raw body, transformed payload mismatch, or body modified by middleware.
  • no FastHook signature headers: destination auth_type is not FASTHOOK_SIGNATURE or the destination is CLI.
  • verification passes but event repeats: add idempotency using x-fasthook-event-id or your provider event id.

Verification Checklist

  • Configure source auth for every public provider source.
  • Keep allowed_http_methods as narrow as possible.
  • For HMAC source auth, match the provider's signature header, prefix, timestamp header, and exact signing input.
  • Inspect Requests to confirm accepted requests are verified.
  • Reject unverified or failed signatures before doing business logic in your own receiver.
  • Verify outbound FastHook delivery signatures on HTTP destinations.
  • Use raw body bytes or text for signature checks, then parse JSON.
  • Enforce a timestamp tolerance on FastHook delivery signatures.
  • Store event ids or request ids for idempotency.
  • Rotate secrets immediately after exposure.

Where To Inspect Results

  • Requests: inbound status, verified flag, source, method, headers, body, rejection cause.
  • Events: routed event status, source, connection, destination, method, path.
  • Attempts: destination response status, failure code, retry trigger, response body, delivery timing.
  • Project Secrets: API key and signing secret rotation.
  • Source config: inbound auth_type, auth settings, custom response, allowed methods.
  • Destination config: outbound auth_type and delivery method.

Next