Verify Square Webhook Signatures
Time: ~10 minutes | Difficulty: Beginner | Prerequisites: Square Developer account with a webhook subscription, Slashbin account
TL;DR: Square computes its signature over the notification URL concatenated with the request body — not the body alone. That single difference is why most hand-rolled Square signature checks fail with an error indistinguishable from a wrong key. Route Square through Slashbin, supply the signing key and the notification URL, and verification becomes configuration instead of code.
The Problem
Every other webhook provider you have integrated signs the request body. Square does not.
Square computes HMAC-SHA256(signature_key, notification_url + request_body) and sends the result base64-encoded in the x-square-hmacsha256-signature header. If you sign the body alone — the thing every previous integration taught you to do — you get a digest that does not match.
And the failure is silent about its cause. A signature mismatch looks exactly the same whether:
- you used the wrong signing key,
- you signed the body instead of the URL plus the body,
- your notification URL differs from Square's by a trailing slash,
httpvshttps, or awww., - or you hex-encoded a digest Square sent as base64.
Four different bugs, one error message. Developers lose hours here, and the Square documentation's own sample code is easy to adapt incorrectly.
The Solution
Slashbin's gateway is configuration-driven. There is no Square-specific code path — Square works because its verification contract is stored and applied on every request:
| Field | Square's value |
|---|---|
| Auth type | HMAC |
| Signature header | x-square-hmacsha256-signature |
| Signature encoding | base64 |
| Signature prefix | none |
| Signed payload | notification URL + body |
| Event topic | type, read from the body |
You supply two values — the signing key and the notification URL — and every request is verified against that contract before it is stored or delivered. You write no verification code at all.
Outcome: The url_and_body trap stops being your problem. Requests that fail verification are rejected at the gateway; requests that pass are stored with full payload fidelity.
Step 1: Create a Square Project in Slashbin
- Click Create New Project and select Square.
- Type: Choose Transactional — Square payment and inventory events are stateful business events.
Selecting Square loads its verification contract into the project. The signature header, base64 encoding, and the URL-plus-body signing rule are already set; the wizard only asks you for the secrets it cannot know.
Step 2: Paste the Signature Key and the Notification URL
The Square project asks for two values, and the second one is the part worth understanding.
- HMAC Signing Secret — paste Square's Signature Key, found in the Square Developer Dashboard under your application → Webhooks → Subscriptions → your subscription.
- Notification URL — paste the Slashbin Ingestion URL for this project.
That second field is not bookkeeping. Because Square signs notification_url + request_body, the URL is part of the signed payload. Slashbin cannot reconstruct the digest without knowing the exact string Square used, so you tell it.
The Notification URL must match what you register in Square character for character. https://in.slashbin.io/abc123 and https://in.slashbin.io/abc123/ produce different signatures. So do http vs https and any difference in host. If verification fails after setup, compare these two strings before you suspect the key.
Step 3: Point Square at the Slashbin Ingestion URL
- Copy the Ingestion URL from the Slashbin project dashboard — the same value you pasted in Step 2.
- In the Square Developer Dashboard, open your application → Webhooks → Subscriptions → Add Subscription.
- Paste the Ingestion URL as the Notification URL.
- Select the event types you want to forward — for example
payment.created,payment.updated,inventory.count.updated. - Save.
Square now posts to Slashbin. Slashbin verifies each request against the URL-plus-body digest and stores the raw payload before anything downstream runs.
Step 4: Confirm the First Verified Event
Send a test event from Square, then open the project's Delivery Log in the Slashbin Console. A verified Square event looks like this:
{
"merchant_id": "MLEFBHHSJGVHD",
"type": "payment.updated",
"event_id": "e1d6ae37-5aa9-45a5-b525-b12caf819fdb",
"created_at": "2026-07-21T14:03:22.104Z",
"data": {
"type": "payment",
"id": "KkAkhdMsgzn59SM8A89WgKwekxLZY",
"object": {
"payment": {
"id": "KkAkhdMsgzn59SM8A89WgKwekxLZY",
"status": "COMPLETED",
"amount_money": {
"amount": 4900,
"currency": "USD"
},
"location_id": "S8GWD5R9QB376"
}
}
}
}Two things to note about this payload:
- The topic is the top-level
typefield —payment.updated. That is the value Slashbin reads to route the event, and it is read from the body, not from a header. data.typeis not the topic. It names the object Square is describing (payment), not the event that occurred. Routing rules that read the nested field will match far more broadly than you intended.
If the event does not appear, check the delivery log for a rejected request. A signature failure at the gateway means one of the two values in Step 2 does not match what Square used — and by far the most common cause is the notification URL, not the key.
Why Slashbin vs a Raw Handler
- The
url_and_bodyrule is applied for you. No reimplementation of Square's unusual signing scheme, and no chance of shipping a handler that signs the body alone. - One contract, many providers. Square's base64 digest, GitHub's hex digest with a
sha256=prefix, Stripe's timestamped scheme — all verified by the same gateway from stored configuration, so your handlers never carry provider-specific crypto. - Failures are visible. A rejected request is a log entry with a reason, not a 401 your endpoint returned into the void.
- Verified events are stored. Once a Square event passes verification, its raw payload, transform result, and every delivery attempt are on disk and replayable.
Next Steps
- See what stored evidence buys you when a delivery fails — Debug Stripe Webhooks walks the same delivery log.
- Reshape Square's nested payment object into your internal schema — see Golden Model.
- Send one Square event to more than one system — see Fan-Out Routing.
- Browse every supported source and destination on Integrations.