BlogSquare Webhooks

How to Test Square Webhooks

Square webhooks cover payments, refunds, orders, and catalog updates so back-office systems can react as money and inventory move.

Looking for the broader picture? See the 7 best webhook testing tools (2026), or if you're already on Webhook.site, the 60-second migration to HookRay.

Square Official Webhook Docs

1. Square Webhook Events

Square can send the following webhook events to your endpoint:

payment.created
payment.updated
refund.created
refund.updated
order.created
order.updated
catalog.version.updated

2. Set Up a Test Endpoint with HookRay

Follow these steps to start receiving Square webhooks for testing:

  1. Go to HookRay and click "Start Testing — Free" to get your unique webhook URL.
  2. Copy the URL (e.g., https://h.hookray.com/abc123).
  3. In your Square dashboard, navigate to the webhook settings and paste the HookRay URL as your endpoint.
  4. Select the events you want to receive (see list above).
  5. Trigger a test event — HookRay will show the incoming webhook in real-time.

3. Sample Square Webhook Payload

Here's an example of what a Square webhook payload looks like:

payload.json
{
  "merchant_id": "ML4F4M3A6H4G2",
  "type": "payment.updated",
  "event_id": "v1-payment-3b6e8d6f",
  "created_at": "2026-03-20T10:05:00Z",
  "data": {
    "object": {
      "payment": {
        "id": "R2B3Z4Y5X6",
        "amount_money": {
          "amount": 4200,
          "currency": "USD"
        },
        "status": "COMPLETED"
      }
    }
  }
}

4. How to Verify Square Webhook Signatures

Signing details
Algorithm
HMAC-SHA256
Header
x-square-hmacsha256-signature
Encoding
base64

Square signs `{notification_url}{raw_request_body}` — the full webhook URL concatenated with the body, no separator. The URL must be the exact one Square called.

Node.js (Express)

import crypto from 'node:crypto';
import express from 'express';

const app = express();

app.post(
  '/webhooks/square',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-square-hmacsha256-signature'] as string | undefined;
    if (!signature) return res.status(401).send('missing signature');

    // Reconstruct the public URL Square called (be careful behind proxies).
    const notificationUrl = `https://${req.headers.host}${req.originalUrl}`;
    const stringToSign = notificationUrl + req.body.toString('utf8');

    const expected = crypto
      .createHmac('sha256', process.env.SQUARE_WEBHOOK_SIGNATURE_KEY!)
      .update(stringToSign)
      .digest('base64'); // base64, not hex

    if (
      signature.length !== expected.length ||
      !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
    ) {
      return res.status(403).send('invalid signature');
    }

    res.json({ ok: true });
  },
);

Python (FastAPI)

import hmac, hashlib, base64, os
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

@app.post("/webhooks/square")
async def square_webhook(request: Request):
    body = await request.body()
    signature = request.headers.get('x-square-hmacsha256-signature', '')
    notification_url = str(request.url)  # public-facing URL Square called
    string_to_sign = notification_url.encode() + body
    expected = base64.b64encode(
        hmac.new(
            os.environ['SQUARE_WEBHOOK_SIGNATURE_KEY'].encode(),
            msg=string_to_sign,
            digestmod=hashlib.sha256,
        ).digest()
    ).decode()
    if not hmac.compare_digest(signature, expected):
        raise HTTPException(status_code=403, detail='invalid signature')
    return {'ok': True}
Watch out: Square's signed string includes the full notification URL — not just the body. Reverse proxies that rewrite the host header (Vercel, Cloudflare, ngrok) can break verification because the URL Square called doesn't match the URL your handler sees. Use the URL configured in Square Developer Portal, not request.url, when in doubt.

Capture a real Square webhook with HookRay first, then replay the captured request against your verifier locally — that way you can iterate on the verification code without re-triggering events in Square. Read Square's official signing docs for the canonical reference, or see the cross-service signature verification guide for Ruby and timing-safe comparison patterns.

5. How Square Retries Failed Webhooks

Retry policy
Max attempts
70
Total window
Up to 72 hours
Backoff
Exponential, starts ~immediately
Retries on
5xx, timeout (10s)
Stops on
Any 2xx response. 4xx also stops retries.
Watch out: Square has the most generous retry budget — 72 hours, 70 attempts. This means transient outages of nearly any duration recover automatically. The flip side: if your code is permanently broken, Square will hammer your endpoint 70 times before giving up, so make sure your monitoring catches handler bugs fast.

If your handler is going to see the same Square event multiple times, you need idempotency by event ID and the right response codes — see the Webhook Retry Strategies guide for the full pattern (idempotency tables, dead-letter queues, replaying captured events for tests). Cross-reference with Square's official retry docs before relying on these numbers in production.

6. Frequently Asked Questions

How do I test Square webhooks without deploying?

Use HookRay to get an instant public webhook URL. Paste it into your Square dashboard's webhook configuration, trigger an event, and watch the payload arrive in real time. No code, no ngrok, no deployment required. The free tier captures 100 requests per month and works on all Square event types.

Why aren't my Square webhooks arriving?

The four most common causes: (1) the endpoint URL isn't publicly accessible — Square can't reach localhost; (2) the wrong events are subscribed in your Square dashboard; (3) signature verification is rejecting the request before your handler runs; (4) Square can't reach your server because of a firewall, expired SSL certificate, or wrong DNS. Use HookRay's URL to isolate which of these four is failing — if HookRay receives the webhook, the problem is in your handler. If HookRay doesn't, the problem is in Square configuration.

Why am I getting 400 or 500 errors from my Square webhook?

Square reports the response status your endpoint returned. HookRay accepts any payload and returns 200 OK by default, so if you see 400/500 in your Square dashboard while pointing at HookRay, the issue is in Square's configuration (wrong event, malformed signing secret, etc.). If you point at your own endpoint and get 400/500, the issue is in your handler — capture the request with HookRay, replay it locally, and debug from the captured payload.

How do I verify Square webhook signatures?

Square signs each webhook request with a shared secret. Capture the raw headers and body using HookRay, then verify the signature in your application using Square's SDK or a standard HMAC library. Once verification works against HookRay-captured data, you can safely deploy. Square's docs (linked above) cover the exact signing algorithm.

Can I replay a captured Square webhook?

Yes — HookRay's replay feature re-sends any captured webhook to a different endpoint with one click. This is the fastest way to fix a buggy handler: capture the payload once, fix your code, and replay until it works. No need to re-trigger the event in Square.

7. Next Steps

  • Use HookRay's webhook replay feature to re-send captured webhooks while building your handler
  • Enable smart parsing (Pro plan) to see Square-specific fields highlighted automatically
  • Check the Square webhook documentation for the complete event reference

Ready to test Square webhooks?

Get a free webhook URL in 5 seconds. No signup required.

Start Testing Square Webhooks — Free

Free PDF: Webhook Testing Cheat Sheet 2026

One-page reference for 50+ APIs — canonical events, signing methods, sample payloads. Print it, pin it, share it.

📄 Download the cheat sheet (PDF, 180KB)