Square webhook signature verification failed? Check the URL first

Your endpoint receives Square events but WebhooksHelper.verifySignature() returns false (or your hand-rolled check never matches x-square-hmacsha256-signature). Square's scheme has one property almost no other provider has: the signature covers the notification URL itself, then the raw body. That single fact explains most failed verifications — your code can be byte-perfect and still fail because the URL string you're verifying with isn't the exact URL Square signed.

What Square actually signs

The x-square-hmacsha256-signature header is a base64 HMAC-SHA256 over {notification_url}{raw request body} — concatenated directly, no separator. The key is your webhook subscription's signature key (shown under Webhooks in the Developer Console), used as a plain UTF-8 string.

You can prove the shape to yourself with the example values from Square's own docs (key asdf1234, URL https://example.com/webhook):

printf '%s' 'https://example.com/webhook{"hello":"world"}' \
  | openssl dgst -sha256 -hmac 'asdf1234' -binary | base64
# 2kRE5qRU2tR+tBGlDwMEw2avJ7QM4ikPYD/PJ3bd9Og=

If that one-liner matches the header for a real capture (with your real key and URL), your inputs are right. When it doesn't, one of the causes below is in play.

Cause 1: the URL you verify with ≠ the URL Square signed

Square signs the notification URL exactly as configured in the subscription. Your verifier must be given that exact string. The classic mismatches:

Cause 2: not the raw body

Same rule as Stripe and GitHub: the HMAC is over the raw bytes Square sent. If your framework parses JSON and you re-serialize it (JSON.stringify(req.body)), key order, unicode escaping, or whitespace can change and the signature dies. Capture the raw body before any body-parser touches it.

Cause 3: wrong signature key

Cause 4: verifying the wrong header

Current webhook subscriptions send x-square-hmacsha256-signature (HMAC-SHA256, base64). Very old Square v1 webhook code and samples verified an X-Square-Signature header (HMAC-SHA1). If you copied an old snippet, you're computing the wrong algorithm against the wrong header.

Not a signature problem? Delivery facts worth knowing

Isolate it in one minute with a capture URL

The URL-in-signature property makes Square uniquely awkward to debug with generic HMAC testers — they hash only the body, so they can never validate a Square signature. A Hookden bin can, because the bin knows the exact URL the request hit, which is precisely what Square signed when the subscription points straight at the bin:

  1. Create a bin and set the subscription's notification URL to the bin URL (Developer Console → Webhooks → Subscriptions).
  2. In the bin's settings, choose Square as the signature scheme and paste the subscription's signature key.
  3. Send a test event from the Developer Console. Every capture now shows a ✓/✗ badge: ✓ means key and URL are right and the failure is in your verifier's inputs (almost always Cause 1 or 2). ✗ with a fresh capture means the key is wrong (Cause 3) — the URL can't be wrong here, since the bin verifies with the URL it was actually called on.
  4. The capture shows the exact raw body and every header — compare byte lengths with what your server logged to catch body-parser mangling.

Verifying on localhost? Point the subscription at a bin and relay captures to your machine — the relay re-delivers the byte-identical body and the x-square-* headers, and your local verifier keeps using the bin URL as its NOTIFICATION_URL, so the signature still passes even though the request reached you over the relay instead of a tunnel.

Related: HubSpot signature validation (the other URL-signing scheme), Stripe signature verification failed, Clerk/Svix verification, and testing webhook retry behavior.

No signup needed. Or from your terminal: curl https://hookden.pages.dev/new

← All guides · Signature debugger · Payload examples · Docs · Hookden vs webhook.site