How the webhook URL works. It is a public address that records whatever is sent to it and
answers 200. An inbox keeps the 50 most recent requests, accepts bodies up to 256 KB, and is
deleted 24 hours after it was created — or sooner if you press Delete URL or our server
restarts, because requests are held in memory and never written to disk.
Anyone who has the URL can read what it received. Use sandbox and test-mode accounts, do not route production traffic or customer data through it, and delete it when you are done. Privacy details.
What is a Square webhook?
A Square webhook is an HTTP POST sent to a notification URL you subscribed in the Developer Console when something happens in a seller's account: a payment changes state, an order is updated, a refund is created, a customer record changes. The JSON envelope is the same for every event: type names it, event_id identifies it, and data.object holds the affected resource.
Subscriptions exist separately for the Sandbox and for production, each with its own signature key and its own API version. An event is rendered in the API version the subscription was created with, which is why two subscriptions can describe the same payment with slightly different fields.
What a Square webhook request looks like
The request line and headers as they reach your server, followed by the body. Ids, tokens and the signature are placeholders.
POST /your/webhook/path HTTP/1.1
Host: your-server.example
x-square-hmacsha256-signature: 2kRE5qRU2tR+tBGlDwMEw2avJ7QM4ikPYD/PJ3bd9Og=
square-environment: Sandbox
square-initial-delivery-timestamp: 2026-01-01T12:00:00Z
square-retry-number: 1
square-retry-reason: http_timeout
Content-Type: application/json
{
"merchant_id": "MLEXAMPLE00001",
"type": "payment.updated",
"event_id": "6a8f5f28-54a1-4eb0-a98a-3111513fd4fc",
"created_at": "2026-01-01T12:00:00Z",
"data": {
"type": "payment",
"id": "KkAkhdMsEXAMPLE0000001",
"object": {
"payment": {
"id": "KkAkhdMsEXAMPLE0000001",
"status": "COMPLETED",
"source_type": "CARD",
"amount_money": {
"amount": 2000,
"currency": "USD"
},
"total_money": {
"amount": 2000,
"currency": "USD"
},
"location_id": "LEXAMPLE000001",
"order_id": "oEXAMPLE00000000000001",
"version": 3,
"created_at": "2026-01-01T11:59:58Z",
"updated_at": "2026-01-01T12:00:00Z"
}
}
}
}
Money amounts are integers in the currency's smallest unit, so 2000 with USD is 20.00. A single payment produces several payment.updated events as it moves from approved to completed; version rises each time, which lets you ignore an older snapshot that arrives late.
Headers Square sends
| Header | Example | What it is |
|---|---|---|
x-square-hmacsha256-signature | 2kRE5qRU2tR+tBGlDwMEw2avJ7QM4ikPYD/PJ3bd9Og= | HMAC-SHA256, base64, over the notification URL followed by the body. |
square-environment | Sandbox | Sandbox or Production. |
square-initial-delivery-timestamp | 2026-01-01T12:00:00Z | When the first attempt for this event was made. |
square-retry-number | 1 | Present only on a retry: which retry this is. |
square-retry-reason | http_timeout | Present only on a retry: why the previous attempt failed. |
Content-Type | application/json | The body is JSON. |
Header names are case-insensitive. Your framework may show them lower-cased, and HTTP/2 always does.
How Square signs a webhook
Each subscription has a signature key. Square takes the notification URL exactly as it is saved on the subscription, appends the raw request body, computes an HMAC-SHA256 of that string with the key, and sends the base64 digest in x-square-hmacsha256-signature.
Because the URL is signed, verification needs the registered address as a constant in your configuration; do not rebuild it from the incoming request, where a proxy may have changed the scheme, host or trailing slash. Compute it over the raw request body exactly as received — before any JSON parsing — and compare with a constant-time function.
const crypto = require('crypto');
// notificationUrl: the URL exactly as saved on the webhook subscription
// rawBody: string of the body exactly as received
function verifySquare(rawBody, signature, notificationUrl, signatureKey) {
const expected = crypto.createHmac('sha256', signatureKey)
.update(notificationUrl + rawBody).digest('base64');
const a = Buffer.from(signature || '');
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Timeouts, retries and duplicates
Answer with a 2xx as soon as the request is stored. Square's documentation at the time of writing gives an endpoint ten seconds to respond, and treats a slower reply or any other status as a failure. Failed notifications are retried with a growing delay for up to a day, each retry marked with square-retry-number and square-retry-reason.
Retries reuse the same event_id, so that is the value to record for de-duplication. Square does not promise ordering either: compare version or updated_at on the resource rather than trusting arrival order.
Catch a real Square webhook
- Press Create webhook URL at the top of this page and copy the address.
- In the Square Developer Console, open an application and switch to Sandbox.
- Under Webhooks → Subscriptions, add a subscription, paste the address as the notification URL and choose events such as
payment.updated. - Use the console's option to send a test event, or take a Sandbox payment through the API.
- Read the request in the inbox above, including the signature and environment headers.
The signature key is shown on the subscription's page. To verify a caught request, pair it with the exact URL you pasted — the address of the inbox, since that is the URL Square signed.
To run a caught request against your own code, select it and press Open in API Tester: the method, headers and body are loaded into the request console, where you enter your server's public URL and send it. A body you edit will no longer match its signature — that is the signature doing its job.
Things that catch people out
- A trailing slash is a different URL. The signed string uses the address as registered, character for character.
- Sandbox and production do not share keys. A handler deployed with the Sandbox signature key rejects every production event.
- There is an older header too.
x-square-signatureis the legacy HMAC-SHA1 variant; verify the SHA-256 one.
api-tester.com is independent and is not affiliated with Square. Providers change header names, limits and retry schedules; treat the official Square documentation as the source of truth.
Related examples and tools
Bitbucket Webhook Example
Bitbucket Cloud webhooks look like GitHub's at first glance and differ in the details that break copied code: the event name header, the signature header's name, and the shape of a push.
Jira Webhook Example
A Jira webhook delivers the whole issue plus a changelog of exactly what was edited.
Mailgun Webhook Example
Mailgun puts its signature inside the JSON body instead of in a header, and that signature covers a timestamp and a token rather than the event itself.
Typeform Webhook Example
A Typeform webhook sends one form submission: the questions as they were defined and the answers as they were given, in two separate arrays you have to join yourself.
Calendly Webhook Example
Calendly webhooks are created through the API rather than a settings page, and they describe everything by URI.
Base64 Encoder
Convert a digest between hex and base64 when a signature comparison will not line up.
Curl POST JSON
Send a JSON body like this one from a terminal.
Webhook Tester
The inbox on its own page, plus a guide to signatures, retries and idempotent handlers.
All Webhook Examples
30 providers side by side: content type, signature scheme and retry behaviour.
Other webhooks that sign the URL as well
References
- Official Square documentation — The provider's own guide to the requests described on this page.
- Square Developer: verify and validate an event notification — The signature check, with code.
- Square Developer: webhook events reference — Every event type, by API.
- Square Developer: troubleshoot webhooks — What to check when notifications do not arrive.
- RFC 2104: HMAC — The keyed-hash construction this signature is built on.
- Node.js: crypto.timingSafeEqual() — The constant-time comparison used in the code on this page.
These links leave api-tester.com, which is independent of the companies and projects they point to.
Square webhook FAQ
What does a Square webhook payload look like?
A JSON object with merchant_id, type, event_id, created_at and a data object. data.type names the resource, data.id is its id and data.object contains the resource itself, for example a payment.
How do I verify a Square webhook signature?
Concatenate the notification URL exactly as registered with the raw request body, compute an HMAC-SHA256 using the subscription signature key, base64-encode the result and compare it with x-square-hmacsha256-signature.
Why does Square signature validation fail?
Usually because the URL used in the calculation differs from the one saved on the subscription, for example http instead of https or a missing trailing slash, or because the body was parsed and re-serialised.
Does Square retry webhooks?
Yes. A non-2xx response or a timeout is retried with increasing delays for up to a day according to Square's documentation at the time of writing. Retries reuse the same event_id.
How do I test Square webhooks?
Create a Sandbox subscription in the Developer Console and send a test event from there, or make Sandbox API calls that trigger the events you subscribed to.