Skip to content
Back to blog
ProductSignaturesDashboardNew Feature

New: Verify Webhook Signatures Without Writing Code

Paste a signing secret in the dashboard and verify any captured webhook instantly — or configure it on the endpoint and every request is verified automatically. Supports Stripe, GitHub, Shopify, and 10 more providers.

Apr 15, 20265 min read

"Why is my signature verification failing?" is the most common webhook debugging question. You capture the webhook, see the headers and body, compare your secret, re-read the provider docs, and still can't figure out why constructEvent throws.

The problem is that signature verification is invisible. You can't see the expected hash, the received hash, or whether the timestamp is stale. You're debugging blind.

Today we're shipping signature verification as a first-class feature in webhooks.cc. Two modes: paste a secret and verify any request instantly in the dashboard, or configure it on the endpoint and every incoming webhook is verified automatically.

Mode 1: Verify in the browser

Open any captured request, click the Signature tab, paste your signing secret, and click Verify.

The verification runs entirely in your browser. Your secret is never sent to our servers — it stays in the page until you close the tab.

The dashboard auto-detects the provider from the request headers. If it sees stripe-signature, it selects Stripe. If it sees x-hub-signature-256, it selects GitHub. You can override the detection if needed.

When verification fails, the dashboard shows you exactly what went wrong:

  • Expected vs. received signature — the computed hash and the hash from the header, side by side, with copy buttons
  • Timestamp analysis — for providers that include timestamps (Stripe, Slack, Paddle), whether the request falls within the 5-minute tolerance window
  • Provider-specific debugging tips — Stripe tells you to check the endpoint-specific secret vs. the global one; GitHub reminds you about the sha256= prefix

This is for one-off debugging. You captured a webhook, something's wrong, you want to check if the signature is valid right now.

Mode 2: Automatic verification on every request

Open your endpoint's settings and configure a signing provider and secret in the Signature Verification section. Select the provider from the dropdown, paste the secret, and save.

The secret is encrypted with AES-256-GCM before it touches the database. It's never visible again after you save it — the API returns hasSigningSecret: true, never the plaintext.

Once configured, the Rust receiver verifies every incoming request automatically. Verification runs as an async task after the response is sent — it adds zero latency to the webhook delivery. Results are stored on each request and surfaced everywhere:

  • Dashboard request list — a shield icon on each request: green for valid, red for invalid, nothing when verification isn't configured
  • Signature tab — shows the stored result with full details (no need to paste a secret)
  • SDKrequest.signatureVerified is true, false, or null
  • CLI tunnel — forwarded requests include X-Signature-Verified and X-Signature-Provider headers
  • MCP — AI agents see verification status on every request

13 providers supported

ProviderAlgorithmNotes
StripeHMAC-SHA256Timestamp in stripe-signature header
GitHubHMAC-SHA256sha256= prefix on signature
ShopifyHMAC-SHA256Base64-encoded signature
TwilioHMAC-SHA1Signs URL + sorted form params
SlackHMAC-SHA256Timestamp prefix: v0:ts:body
PaddleHMAC-SHA256Timestamp in paddle-signature header
LinearHMAC-SHA256Standard hex signature
VercelHMAC-SHA1Hex-encoded
GitLabToken comparisonx-gitlab-token header, not HMAC
ClerkHMAC-SHA256Standard Webhooks format via svix headers
DiscordEd25519Public key, not shared secret
Standard WebhooksHMAC-SHA256Base64 secret, whsec_ prefix
Generic HMACHMAC-SHA256You specify the header name

SendGrid uses IP allowlisting instead of signatures. It doesn't appear in the provider dropdown — instead, a note below the selector explains that SendGrid is not supported for signature verification.

Generic HMAC handles any provider not in the list — point it at the header that contains the signature and it verifies with HMAC-SHA256.

Three ways to configure

You can set up verification through the dashboard, the SDK, or the REST API.

Dashboard — open endpoint settings, select a provider, paste the secret. Good for quick setup and one-off debugging.

SDK / programmatic — configure signing from code, with secrets in your .env files where they belong:

import { WebhooksCC } from "@webhooks-cc/sdk";
 
const client = new WebhooksCC({ apiKey: process.env.WHK_API_KEY! });
 
await client.endpoints.update("my-endpoint", {
  signingProvider: "stripe",
  signingSecret: process.env.STRIPE_WEBHOOK_SECRET!,
});

REST API — the same thing via curl, useful in CI or setup scripts:

curl -X PATCH https://webhooks.cc/api/endpoints/my-endpoint \
  -H "Authorization: Bearer $WHK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"signingProvider": "stripe", "signingSecret": "'$STRIPE_WEBHOOK_SECRET'"}'

All three produce the same result: the secret is encrypted with AES-256-GCM and stored on the endpoint. Every incoming request is verified automatically from that point on.

Asserting on verification in tests

import { matchAll, matchVerified, matchMethod } from "@webhooks-cc/sdk";
 
const request = await client.requests.waitFor("my-endpoint", {
  timeout: "30s",
  match: matchAll(matchMethod("POST"), matchVerified()),
});
 
// request.signatureVerified === true
// request.signingProvider === "stripe"

matchVerified() filters for requests where the signature was valid. matchUnverified() filters for requests where verification failed. Requests with no signing config (signatureVerified === null) match neither.

To clear the signing config:

await client.endpoints.update("my-endpoint", {
  signingProvider: null,
});

The on-ramp: browser to endpoint

The three paths serve different stages of a project. During initial integration, you paste a secret in the browser to debug one request. Once the signature checks out, you save it to the endpoint — through the dashboard, the SDK, or the API — and verification becomes automatic.

A typical progression:

1

Capture a webhook

Point Stripe at your webhooks.cc endpoint. A webhook arrives.

2

Verify in the browser

Open the request, go to the Signature tab, paste your whsec_... secret, click Verify. The signature checks out.

3

Save to endpoint

Click "Save to Endpoint Settings" at the bottom of the Signature tab. The endpoint settings dialog opens with Stripe pre-selected. Paste the secret again (it wasn't stored from the browser step), save.

4

Automatic from here

Every future request is verified automatically. Green shields in the request list. signatureVerified: true in the SDK. X-Signature-Verified: true in the CLI tunnel.

What it looks like when things go wrong

The value of this feature is clearest when verification fails. Instead of a generic "signature mismatch" error from your Stripe SDK, the dashboard shows:

  • The exact signature your server computed vs. what Stripe sent
  • Whether the timestamp was stale (outside the 5-minute window)
  • Whether the signature header was missing entirely (the request may not be from Stripe)
  • A provider-specific tip pointing you to the most likely fix

This turns "why is my verification failing?" from a 30-minute debugging session into a 10-second glance.

Signature Verification Guide

Full setup guide for browser and endpoint verification modes.