Hookbase
LoginGet Started Free
Back to Blog
Debugging

Debugging "Invalid Signature" Errors Across Providers

You wired up a webhook, the signature check fails on every request, and you have no idea why. Here is the systematic checklist that finds the bug in under five minutes.

Hookbase Team
April 16, 2026
5 min read

The Symptom

You added signature verification to your webhook handler. Now every request returns 401. You're sure the secret is right. The math looks right. You've copied the example from the docs.

In our experience, the bug is almost always one of seven things. Walk this list in order — the first match is usually the answer.

The Checklist

1. Are You Hashing the Raw Body?

This is the #1 cause across every framework, every language. The signature is computed over the exact bytes the provider sent. If your framework parsed the body into an object and you're hashing the re-serialized version, the bytes don't match — even though the JSON is "the same."

Quick test: Log req.body.length (raw bytes) and compare to what the provider sent (most providers show this in their dashboard). If they differ even by one byte, your body is being mutated before you hash it.

Fix: Read the raw body before any parsing middleware sees it. Express: express.raw({ type: 'application/json' }). Flask: request.get_data(). Rails: request.body.read. Next.js Route Handlers: await request.text().

2. Hex vs. Base64

Each provider chose one and not the other. Confusing them produces a wrong hash that's exactly the right length, so the comparison silently fails.

| Provider | Encoding | |----------|----------| | Stripe | hex | | GitHub | hex | | Shopify | base64 | | Square | base64 | | Slack | hex | | HubSpot | base64 |

Fix: Check your provider's docs and match exactly. The encoding is usually one parameter at the end of your hash function call.

3. Wrong Secret

There's almost always more than one secret in your account. Webhook signing secrets, API keys, OAuth client secrets, signing keys — they're not interchangeable.

Stripe specifically: Test mode and live mode have different webhook signing secrets. They look identical (whsec_... prefix). Sending live events to a handler configured with the test secret will fail every signature check.

Shopify specifically: The webhook secret is shown only once at creation time. If you didn't save it, you have to delete the webhook and create a new one.

Fix: Re-fetch the secret from the provider's dashboard, paste it directly into a temporary string in your code, and try once. If that works, your secret loading mechanism (env vars, secret manager, etc.) is the bug — not your verification logic.

4. Comparison Type

Stripe sends Stripe-Signature: t=...,v1=.... If you compare the entire header to your computed hash, it'll never match — you have to parse out the v1 value first.

Several providers prefix their signature: GitHub sends sha256=abc.... The hash is the part after sha256=. You either compare against the full prefixed string (and prefix yours too) or strip the prefix on both sides.

Fix: Print both the expected signature and your computed one side by side. The mismatch is usually obvious at a glance.

5. Signature Includes the Timestamp

Stripe and Slack both require you to hash a combination of timestamp and body, not just body.

  • Stripe: HMAC-SHA256(secret, "{timestamp}.{body}") — note the period
  • Slack: HMAC-SHA256(secret, "v0:{timestamp}:{body}") — note the version prefix

If you're hashing only the body for these two, every request will fail.

6. Character Encoding Mismatch

You read the body as a string in UTF-16 or with BOM stripping. The bytes you hash are not the bytes that arrived. This is rarer in modern frameworks but still happens with custom middleware.

Fix: Hash bytes, not strings. If your language forces strings, use UTF-8 explicitly and verify there's no BOM or normalization happening.

7. Constant-Time Comparison Failing on Length Mismatch

Some constant-time comparison functions throw or return false if the two inputs have different lengths — which they will if you're comparing hex (64 chars) to base64 (44 chars). This presents as "signature invalid" but the real problem is encoding mismatch (see #2).

Fix: Log both lengths. If they differ, encoding is wrong.

A Diagnostic Snippet

Drop this in temporarily to see exactly what's happening:

console.log({
  bodyLength: req.body.length,
  bodyFirstBytes: req.body.slice(0, 50).toString(),
  signatureHeader: req.header('X-Provider-Signature'),
  computedSignature: hmac(req.body, secret),
  secretLength: secret.length,
});

Within one or two requests, the bug becomes obvious. Remove the log before shipping — the body and signature are sensitive.

When to Stop Debugging Yourself

If you're integrating more than two providers, you'll do this dance more times than you'd like. Hookbase verifies the provider's signature on the way in and re-signs with a single Hookbase scheme on the way out. One verification path in your code, instead of one per provider.

Get started free.

webhooksdebuggingsignature-verificationtroubleshooting

Related Articles

Tutorial

Shopify Webhook Signature Verification, Explained

Shopify HMAC verification trips up almost every first-time integrator. Here is exactly how the signature is computed, what goes wrong, and a working implementation in Node, Python, Go, and Ruby.

Reference

Webhook Retries: What Every Provider Does Differently

Stripe retries for 3 days. GitHub gives up after one failure. Shopify retries 19 times. Knowing the rules for each provider is the difference between losing events and not. A reference table plus what it means for your handler.

Best Practices

Idempotency Keys for Webhooks: A Practical Guide

Webhooks get retried. Without idempotency, that means duplicate orders, double charges, and angry customers. Here is how to design a deduplication strategy that actually works.

Ready to Try Hookbase?

Start receiving, transforming, and routing webhooks in minutes.

Get Started Free
Hookbase

Reliable webhook infrastructure for modern teams. Built on Cloudflare's global edge network.

Product

  • Features
  • Pricing
  • Use Cases
  • Integrations
  • ngrok Alternative

Resources

  • Documentation
  • API Reference
  • CLI Guide
  • Blog
  • FAQ

Free Tools

  • All Tools
  • Webhook Bin
  • HMAC Calculator
  • JSONata Playground
  • Cron Builder
  • Payload Formatter
  • Local Testing

Legal

  • Privacy Policy
  • Terms of Service
  • Contact
  • Status

© 2026 Hookbase. All rights reserved.