Square webhooks: the application model, the signature, and how to see them
Square's webhooks are well designed, and two things about them catch most developers out the first time: they belong to your application rather than a store, and they are signed over the URL as well as the body. Understand those two and the rest follows.
Subscriptions belong to the application
You do not point a webhook at a store. You create a subscription on
your Square application
(POST /v2/webhooks/subscriptions)
with a list of event types and one notification URL, and from then on
every merchant who has authorised the application delivers to that
same URL. Each event names its seller in
merchant_id and its
subscription in
square-subscription-id, and
routing by those two is the work an integration does on every delivery.
There is no challenge or confirmation step, so a subscription is live
the moment it is created.
Event types are dot-notation names spanning the whole platform, from
payment.updated and
order.created to
inventory.count.updated,
invoice.payment_made and
booking.updated. One
subscription carries as many as you like, and the full catalogue is at
GET /v2/webhooks/event-types.
The signature covers the URL, not just the body
This is the one that trips people up. Square computes the
x-square-hmacsha256-signature
as a base64 HMAC-SHA256 over the notification URL concatenated with the
raw body, keyed with the signing key Square returned when the
subscription was created. Reach for the body-only verification you use
elsewhere and every delivery reads as tampered. The key is per
subscription, so a delivery's
square-subscription-id is
how you pick the right one, and the comparison should be constant-time.
Retries run for a day, and nothing gets disabled
Square expects a prompt 2xx. When it does not get one, it retries on an exponential backoff for up to 24 hours before giving up on that delivery, and it never disables the subscription for repeated failures. A handler that struggles for an afternoon costs you only the deliveries that exhaust their day, not the subscription itself. A destination that always answers and captures each delivery removes even that.
The headers that matter
x-square-hmacsha256-signature: HMAC-SHA256 over the notification URL plus the body, keyed with the subscription's signing keysquare-subscription-id: which subscription sent it, and so which key verifies itsquare-environment: Sandbox or Productionsquare-retry-number: present only on a retry, so a duplicate is recognisable
Debugging questions
- Why are my Square webhooks not arriving?
- Square does not disable a subscription after failures, so a dead subscription is rarely the cause. Check that the subscription is enabled and its notification URL is HTTPS and publicly reachable, and that you are looking at the right environment: sandbox events never appear against a production subscription, or the reverse. Square retries a failed delivery for up to 24 hours, so a handler that was down briefly will still receive the backlog.
- How do I verify a Square signature?
- Each delivery carries x-square-hmacsha256-signature: a base64 HMAC-SHA256 computed over the notification URL concatenated with the raw request body, keyed with the subscription signing key Square returns when the subscription is created. Compute the same HMAC and compare in constant time. Note the key is per subscription, and the value signed includes the URL, not just the body.
- What is square-subscription-id for?
- Every delivery names the subscription it came from in the square-subscription-id header. Since the signing key is per subscription, that header is how you find the right key to verify with, and how you tell deliveries from two subscriptions apart when they share an endpoint.
- Do sandbox and production differ?
- They are separate accounts with separate access tokens and separate API hosts (connect.squareupsandbox.com and connect.squareup.com). A subscription is created against one of them, and each delivery announces which in the square-environment header, so a captured event is never ambiguous about where it came from.
Seeing the deliveries
CommerceHook gives a Square application a stable HTTPS destination and
shows every delivery in an inspector: the event badge, every header
including the signature, and the payload as a JSON tree or raw. Because
Square returns the signing key at creation, deliveries you register
through us verify automatically. Registration happens
from the dashboard,
deliveries can be replayed at any handler, and
commercehook listen
streams them into the one on your laptop. Free for one endpoint, no
card.