New — webhooks your AI agents can wait on. Hook & Sense opening to early access.

features

Signature Verification

Verify HMAC signatures from Stripe, GitHub, Shopify, and custom providers automatically — exposed to agents as verify_signature.

Webhook providers sign requests with a shared secret so the receiver can verify the request really came from them. HookSense verifies signatures automatically on every incoming request — and the same logic is the reference implementation you'll want to ship in your own handler.

Supported Providers

ProviderHeaderAlgorithm
StripeStripe-SignatureHMAC-SHA256 with timestamp (t=...,v1=...)
GitHubX-Hub-Signature-256HMAC-SHA256 hex, prefixed sha256=
ShopifyX-Shopify-Hmac-SHA256HMAC-SHA256 base64
CustomAuto-detectedHMAC-SHA256 hex

Setup in HookSense

  1. Open your endpoint page and click the Shield icon in the toolbar
  2. Paste your signing secret (e.g., whsec_... for Stripe)
  3. Select the provider or leave on Auto-detect
  4. Save — every new request will show a Verified or Invalid badge

HookSense uses constant-time comparison to prevent timing attacks. Unverified requests are still captured, but they're flagged so you can filter them out with the Verified only toggle.

Verifying in Your Own Handler

HookSense forwards webhooks to your server with the original signature header intact. Implement the same check on your side so production traffic is protected after you stop using HookSense as a proxy.

Stripe (Node.js)

import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET);
const secret = process.env.STRIPE_WEBHOOK_SECRET; // whsec_...

app.post("/webhooks/stripe", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["stripe-signature"];
  try {
    const event = stripe.webhooks.constructEvent(req.body, sig, secret);
    // ... handle event.type
    res.json({ received: true });
  } catch (err) {
    res.status(400).send(`Webhook Error: ${err.message}`);
  }
});

Stripe's library checks both the signature and the timestamp (5-minute tolerance). Always pass the raw body — JSON parsing first will break verification.

GitHub (Node.js)

import crypto from "node:crypto";
const secret = process.env.GITHUB_WEBHOOK_SECRET;

function verify(req) {
  const sig = req.headers["x-hub-signature-256"]; // "sha256=..."
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(req.rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Shopify (Node.js)

import crypto from "node:crypto";
const secret = process.env.SHOPIFY_WEBHOOK_SECRET;

function verify(req) {
  const sig = req.headers["x-shopify-hmac-sha256"];
  const expected = crypto
    .createHmac("sha256", secret)
    .update(req.rawBody, "utf8")
    .digest("base64");
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Generic / Custom (Node.js)

import crypto from "node:crypto";

function verify(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected)
  );
}

Stripe (Python / Flask)

import stripe
from flask import request, abort

@app.post("/webhooks/stripe")
def stripe_webhook():
    payload = request.data  # raw bytes
    sig = request.headers.get("Stripe-Signature")
    try:
        event = stripe.Webhook.construct_event(payload, sig, os.environ["STRIPE_WEBHOOK_SECRET"])
    except (ValueError, stripe.error.SignatureVerificationError):
        abort(400)
    # handle event["type"]
    return "", 200

Common Pitfalls

  • Parsing JSON before verifying — most frameworks do this by default. Use raw body middleware (express.raw, Flask request.data, etc.) on the webhook route only.
  • String comparison instead of constant-time — opens you to timing attacks. Always use crypto.timingSafeEqual / hmac.compare_digest.
  • Using the wrong secret — Stripe has separate signing secrets per webhook endpoint and per environment (test/live). Match them carefully.
  • Trusting unverified requests during dev — when HookSense shows "Invalid", check the secret first; never disable verification "just for now."

Verification is available on Hook plans and above.

Frequently asked

Which providers does HookSense auto-detect signatures for?
Stripe (Stripe-Signature, HMAC-SHA256 with timestamp), GitHub (X-Hub-Signature-256, HMAC-SHA256), Shopify (X-Shopify-Hmac-SHA256, HMAC-SHA256 base64), Twilio, Paddle, Linear, plus a custom mode where you specify the header name and HookSense computes HMAC-SHA256 over the raw body.
Why is my Stripe signature showing as Invalid even with the correct secret?
The most common cause is body mutation: a proxy or middleware reformatted the JSON before HookSense saw it, so the bytes don't match what Stripe signed. HookSense verifies against the raw body before any parsing. If you're proxying through Cloudflare or similar, confirm the signature header is forwarded and the body isn't being re-serialized.
Does HookSense block unverified requests or just label them?
By default HookSense captures every request and labels the verification result (Verified / Invalid / Not configured). It does not reject unverified webhooks at the edge — your handler does that. Use the verified flag in the API or the Verified filter in the UI to focus on signed traffic only.
Is HMAC verification available on the free plan?
No — signature verification requires the Hook plan or above ($29/mo). All paid tiers (Hook, Sense, Enterprise) include it, and agents reach it via verify_signature. The Catch tier captures every callback but doesn't verify signatures.
What's the difference between HookSense's HMAC verification and computing it myself?
Functionally identical — HookSense uses Node's crypto.timingSafeEqual for comparison, the same primitive your server should use. The win is visibility: you see the verification result on every captured request without having to log it from your handler, which makes debugging signature mismatches dramatically faster.