Guide to Square Webhooks: Features and Best Practices
Square webhooks notify your application about payments and commerce activity: a payment is created or updated, an order changes, an invoice is paid, a customer record changes. If you're building on Square, webhooks are how you react to these events without polling.
This guide covers how Square webhooks work, the events you'll handle, how to verify the x-square-hmacsha256-signature, and the best practices for production.
What are Square webhooks?
Square webhooks are JSON POSTs delivered to a URL you register per subscription. Each is signed with an x-square-hmacsha256-signature header: a base64 (not hex) HMAC-SHA256. Square is one of the few providers that signs the notification URL as well as the raw body, so the same payload delivered to a different URL won't validate. The exact concatenation order isn't stated anywhere in Square's docs, all the samples call an SDK helper that takes the URL and body as separate parameters, so use the SDK rather than hand-rolling an order.
Square webhook features
| Feature | Details |
|---|---|
| Configuration | Developer dashboard > your application > Webhooks (per subscription) |
| Signature header | x-square-hmacsha256-signature |
| Signature scheme | Base64 HMAC-SHA256 over the notification URL + raw body (order undocumented; use the SDK) |
| Event field | Top-level type (dotted) |
| Retries | 11 attempts, exponential backoff over 24h, then discarded (recover via the Events API) |
| Keys | Each subscription has its own signature key |
| SDK | npm square (v40+ rewrite); pip squareup |
Common events
Square reports the event type in the top-level type field (dotted):
| Event | Fires when |
|---|---|
payment.created / payment.updated | A payment is created or updated |
order.created / order.updated | An order changes |
invoice.created / invoice.payment_made | An invoice is created or paid |
customer.created / customer.updated / customer.deleted | A customer record changes |
refund.created | A refund is created |
The envelope carries merchant_id, type, event_id, created_at, and data{type, id, object}.
Setting up Square webhooks
In the Square Developer dashboard, open your application > Webhooks, add a subscription with your HTTPS notification URL, and choose the events. Copy that subscription's signature key into SQUARE_WEBHOOK_SIGNATURE_KEY, and set your exact notification URL as SQUARE_WEBHOOK_URL. Each subscription has its own key, so don't reuse one key across subscriptions.
Securing Square webhooks
Because the signed content includes the notification URL and the exact concatenation order isn't documented, use the official SDK helper, which takes the signature key, the notification URL, and the raw body as separate parameters. Verify against the raw body before parsing.
const { WebhooksHelper } = require("square");
const KEY = process.env.SQUARE_WEBHOOK_SIGNATURE_KEY;
const URL = process.env.SQUARE_WEBHOOK_URL; // the exact notification URL for this subscription
app.post("/webhooks/square", express.raw({ type: "application/json" }), async (req, res) => {
const ok = await WebhooksHelper.verifySignature({
requestBody: req.body.toString("utf8"),
signatureHeader: req.headers["x-square-hmacsha256-signature"],
signatureKey: KEY,
notificationUrl: URL,
});
if (!ok) return res.sendStatus(403);
res.sendStatus(200); // acknowledge fast
processQueue.add(JSON.parse(req.body.toString())); // branch on type, async
});
The same check in Python, using the SDK helper:
import os
from square.utilities.webhooks_helper import is_valid_webhook_event_signature
KEY = os.environ["SQUARE_WEBHOOK_SIGNATURE_KEY"]
URL = os.environ["SQUARE_WEBHOOK_URL"]
def verify(raw_body: str, signature_header: str) -> bool:
return is_valid_webhook_event_signature(raw_body, signature_header, KEY, URL)
Square webhook limitations and pain points
The concatenation order is undocumented
The Problem: The signature covers the notification URL plus the raw body, but Square's docs never state the order or operator, so a from-scratch implementation is guessing.
Why It Happens: All of Square's samples delegate to an SDK helper that takes the URL and body as separate parameters.
Workarounds:
- Use the SDK helper (
WebhooksHelper.verifySignature/is_valid_webhook_event_signature) rather than reconstructing a string yourself.
How Hookdeck Can Help: Hookdeck verifies Square deliveries at the edge, so your app doesn't depend on an undocumented construction.
The signature depends on the exact URL
The Problem: Because the notification URL is signed, the same payload delivered to a different URL won't validate, and whether the URL must match byte-for-byte (trailing slash, query, scheme) isn't documented.
Why It Happens: Square binds the destination URL into the signature.
Workarounds:
- Pass the exact notification URL you registered to the SDK helper, and keep it stable.
How Hookdeck Can Help: Hookdeck gives you one stable endpoint and verifies before forwarding, so URL sensitivity isn't your app's problem.
Each subscription has its own key
The Problem: Pointing multiple subscriptions at one listener URL and validating every delivery against a single key fails, because each subscription has its own signature key.
Why It Happens: Square scopes the signature key per subscription.
Workarounds:
- Track which subscription a delivery belongs to and verify with that subscription's key.
How Hookdeck Can Help: Hookdeck can verify each source with its own key, so overlapping subscriptions don't collide.
Retries expire after 24 hours
The Problem: Square retries 11 times with exponential backoff over 24 hours, then discards the event. A prolonged outage means events are gone from the webhook stream.
Why It Happens: Square caps redelivery at 24 hours.
Workarounds:
- Acknowledge reliably, and recover missed events via the Events API rather than redelivery.
How Hookdeck Can Help: Hookdeck durably queues events and retries on its own schedule, so a downstream outage doesn't drop events at Square's 24-hour cap.
Best practices
Verify with the SDK helper over the raw body
Use WebhooksHelper.verifySignature / is_valid_webhook_event_signature, passing the signature key, exact notification URL, and raw body.
Use the right key per subscription
Each subscription has its own signature key; don't share one across subscriptions.
Recover via the Events API
Retries expire after 24 hours, so reconcile missed events through the Events API.
Acknowledge fast, process asynchronously
Return 2xx quickly and defer work to a queue; dedupe on event_id. See why to process webhooks asynchronously.
Make Square webhooks production-ready
Hookdeck verifies the Square signature, deduplicates, and durably queues every event
Conclusion
Square webhooks are verified with an x-square-hmacsha256-signature base64 HMAC-SHA256 over the notification URL plus the raw body. Because the concatenation order is undocumented, use the official SDK helper with the URL and body as separate parameters, verify with each subscription's own key, dispatch on the dotted type, and recover expired retries via the Events API.
Hookdeck verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique events.
Get started with Hookdeck for free and handle Square webhooks reliably in minutes.