Square Integration
Receive and route Square webhooks for payments, orders, customers, invoices, and inventory events.
Setup
1. Create a Source in Hookbase
curl -X POST https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Square Production",
"slug": "square",
"provider": "square",
"signingSecret": "your-square-signature-key",
"rejectInvalidSignatures": true
}'Info
Square issues the signature key, not Hookbase. You get it only after creating the subscription
in step 2, so you can create the source first and set the key afterwards with
PATCH /api/sources/{id} and {"signingSecret": "..."}. Until the real key is set, events are
stored with signature_valid: false — leave rejectInvalidSignatures off until then.
Save your webhook URL:
https://api.hookbase.app/ingest/{orgSlug}/square2. Configure Square Webhook
- Go to the Square Developer Dashboard
- Select your application
- Navigate to Webhooks in the left sidebar
- Click Add Subscription
- Enter your Hookbase webhook URL as the Notification URL
- Select the events you want to receive
- Click Save
- Copy the Signature key from the subscription details
- Paste it into the Hookbase source as its signing secret (dashboard, or
PATCH /api/sources/{id}withsigningSecret)
3. Create Routes
# Create destination for your payment handler
curl -X POST https://api.hookbase.app/api/destinations \
-H "Authorization: Bearer whr_your_api_key" \
-d '{"name": "Payment Service", "slug": "payment-service", "url": "https://api.myapp.com/webhooks/square"}'
# Create destination for order processing
curl -X POST https://api.hookbase.app/api/destinations \
-H "Authorization: Bearer whr_your_api_key" \
-d '{"name": "Order Service", "slug": "order-service", "url": "https://api.myapp.com/webhooks/square-orders"}'
# Create route for payment events
curl -X POST https://api.hookbase.app/api/routes \
-H "Authorization: Bearer whr_your_api_key" \
-d '{"name": "Square Payments", "sourceId": "src_...", "destinationId": "dst_..."}'
# Create route for order events
curl -X POST https://api.hookbase.app/api/routes \
-H "Authorization: Bearer whr_your_api_key" \
-d '{"name": "Square Orders", "sourceId": "src_...", "destinationId": "dst_..."}'Signature Verification
Square signs webhooks using HMAC-SHA256. The signature is sent as a base64-encoded value in the x-square-hmacsha256-signature header.
x-square-hmacsha256-signature: GF4YkfnJpUKBl7AoP9ND9g==Hookbase checks this signature on every request to a source with provider: "square" and records the result on the event as signature_valid. With rejectInvalidSignatures: true, requests that fail are refused with 401.
Signature Computation
Square computes the HMAC-SHA256 signature over the notification URL concatenated with the request body. This means the signed content is:
notification_url + raw_request_bodyThere is no separator between the two and no timestamp. The HMAC key is the subscription's Signature key, and the digest is base64-encoded.
Hookbase uses the exact URL the request arrived on — your /ingest/{orgSlug}/{sourceSlug} URL, or the custom-domain URL if the source uses one — as the signed URL. Square requires it to match byte-for-byte, so register that URL in Square exactly as Hookbase shows it (no extra query string or trailing slash).
Tip
If you also verify in your own handler, sign against the URL registered in Square (your Hookbase ingest URL), not the URL the forwarded request finally reached. Hookbase forwards the raw body unmodified, but the URL your service sees is its own.
Common Events
payment.completed
Triggered when a payment is successfully completed.
{
"merchant_id": "MLF2Z4SFOAXBM",
"type": "payment.completed",
"event_id": "d9b80b48-b8a0-4cf6-96a8-2b7e8a5e3c1d",
"created_at": "2026-03-07T14:30:00.000Z",
"data": {
"type": "payment",
"id": "KkAkhdMsgzn59SM8A4U3sB7MN3F",
"object": {
"payment": {
"id": "KkAkhdMsgzn59SM8A4U3sB7MN3F",
"created_at": "2026-03-07T14:29:45.000Z",
"updated_at": "2026-03-07T14:30:00.000Z",
"amount_money": {
"amount": 1500,
"currency": "USD"
},
"total_money": {
"amount": 1500,
"currency": "USD"
},
"status": "COMPLETED",
"source_type": "CARD",
"card_details": {
"status": "CAPTURED",
"card": {
"card_brand": "VISA",
"last_4": "5858",
"exp_month": 12,
"exp_year": 2027
},
"entry_method": "EMV"
},
"location_id": "LFR7EH5G3XANT",
"order_id": "nPFDY8oPxdeALHQPxIuSHBnqRFo",
"customer_id": "VDKXEEKPJN48QDG3BGGFAK05P8"
}
}
}
}order.created
Triggered when a new order is created.
{
"merchant_id": "MLF2Z4SFOAXBM",
"type": "order.created",
"event_id": "a8b7c6d5-e4f3-2a1b-9c8d-7e6f5a4b3c2d",
"created_at": "2026-03-07T14:28:00.000Z",
"data": {
"type": "order",
"id": "nPFDY8oPxdeALHQPxIuSHBnqRFo",
"object": {
"order_created": {
"created_at": "2026-03-07T14:28:00.000Z",
"order_id": "nPFDY8oPxdeALHQPxIuSHBnqRFo",
"location_id": "LFR7EH5G3XANT",
"state": "OPEN",
"version": 1
}
}
}
}invoice.payment_made
Triggered when a payment is made on an invoice.
{
"merchant_id": "MLF2Z4SFOAXBM",
"type": "invoice.payment_made",
"event_id": "c2d3e4f5-a6b7-8c9d-0e1f-2a3b4c5d6e7f",
"created_at": "2026-03-07T15:00:00.000Z",
"data": {
"type": "invoice",
"id": "inv_0JRTYxBKbYQZfURA2p",
"object": {
"invoice": {
"id": "inv_0JRTYxBKbYQZfURA2p",
"version": 3,
"location_id": "LFR7EH5G3XANT",
"order_id": "nPFDY8oPxdeALHQPxIuSHBnqRFo",
"payment_requests": [
{
"request_type": "BALANCE",
"due_date": "2026-03-15",
"total_completed_amount_money": {
"amount": 5000,
"currency": "USD"
}
}
],
"status": "PAID",
"primary_recipient": {
"customer_id": "VDKXEEKPJN48QDG3BGGFAK05P8"
}
}
}
}
}customer.created
Triggered when a new customer is created.
{
"merchant_id": "MLF2Z4SFOAXBM",
"type": "customer.created",
"event_id": "f1e2d3c4-b5a6-9788-7c6d-5e4f3a2b1c0d",
"created_at": "2026-03-07T12:00:00.000Z",
"data": {
"type": "customer",
"id": "VDKXEEKPJN48QDG3BGGFAK05P8",
"object": {
"customer": {
"id": "VDKXEEKPJN48QDG3BGGFAK05P8",
"created_at": "2026-03-07T12:00:00.000Z",
"updated_at": "2026-03-07T12:00:00.000Z",
"given_name": "Jane",
"family_name": "Doe",
"email_address": "jane.doe@example.com",
"phone_number": "+15555551234",
"preferences": {
"email_unsubscribed": false
}
}
}
}
}inventory.count.updated
Triggered when inventory counts change.
{
"merchant_id": "MLF2Z4SFOAXBM",
"type": "inventory.count.updated",
"event_id": "e7f8a9b0-c1d2-3e4f-5a6b-7c8d9e0f1a2b",
"created_at": "2026-03-07T16:45:00.000Z",
"data": {
"type": "inventory",
"id": "adjustment_1a2b3c4d5e6f",
"object": {
"inventory_counts": [
{
"catalog_object_id": "W62UWFY35CWMYGVWK6TWJDNI",
"catalog_object_type": "ITEM_VARIATION",
"location_id": "LFR7EH5G3XANT",
"quantity": "42",
"state": "IN_STOCK",
"calculated_at": "2026-03-07T16:45:00.000Z"
}
]
}
}
}Transform Examples
Slack Alert for Payments
function transform(payload) {
const payment = payload.data.object.payment;
const amount = (payment.amount_money.amount / 100).toFixed(2);
const currency = payment.amount_money.currency;
const cardBrand = payment.card_details?.card?.card_brand || "N/A";
const last4 = payment.card_details?.card?.last_4 || "****";
return {
blocks: [
{
type: "header",
text: {
type: "plain_text",
text: "Square Payment Received"
}
},
{
type: "section",
fields: [
{
type: "mrkdwn",
text: `*Amount:*\n${currency} $${amount}`
},
{
type: "mrkdwn",
text: `*Status:*\n${payment.status}`
},
{
type: "mrkdwn",
text: `*Card:*\n${cardBrand} ending in ${last4}`
},
{
type: "mrkdwn",
text: `*Location:*\n${payment.location_id}`
}
]
},
{
type: "context",
elements: [
{
type: "mrkdwn",
text: `Payment ID: ${payment.id} | Order: ${payment.order_id || "N/A"}`
}
]
}
]
};
}Database Format
function transform(payload) {
const event = payload;
const data = event.data;
// Handle payment events
if (event.type.startsWith("payment.")) {
const payment = data.object.payment;
return {
square_event_id: event.event_id,
event_type: event.type,
merchant_id: event.merchant_id,
object_id: payment.id,
object_type: "payment",
customer_id: payment.customer_id || null,
order_id: payment.order_id || null,
location_id: payment.location_id,
amount: payment.amount_money.amount,
currency: payment.amount_money.currency,
status: payment.status,
source_type: payment.source_type,
created_at: event.created_at
};
}
// Handle order events
if (event.type.startsWith("order.")) {
const order = data.object.order_created || data.object.order_updated;
return {
square_event_id: event.event_id,
event_type: event.type,
merchant_id: event.merchant_id,
object_id: order.order_id,
object_type: "order",
location_id: order.location_id,
state: order.state,
version: order.version,
created_at: event.created_at
};
}
// Generic fallback
return {
square_event_id: event.event_id,
event_type: event.type,
merchant_id: event.merchant_id,
object_id: data.id,
object_type: data.type,
created_at: event.created_at,
raw_data: data.object
};
}Alert for High-Value Payments
function transform(payload) {
const payment = payload.data.object.payment;
const amount = (payment.amount_money.amount / 100).toFixed(2);
const currency = payment.amount_money.currency;
return {
channel: "#sales-alerts",
username: "Square Bot",
icon_emoji: ":money_with_wings:",
attachments: [{
color: "good",
title: "High-Value Payment Completed",
fields: [
{ title: "Amount", value: `${currency} $${amount}`, short: true },
{ title: "Status", value: payment.status, short: true },
{ title: "Source", value: payment.source_type, short: true },
{ title: "Location", value: payment.location_id, short: true },
{ title: "Customer", value: payment.customer_id || "Guest", short: true },
{ title: "Order", value: payment.order_id || "N/A", short: true }
],
footer: `Payment ${payment.id}`,
ts: Math.floor(Date.now() / 1000)
}]
};
}Filter Examples
Payment Events Only
{
"name": "Payment Events",
"logic": "OR",
"conditions": [
{
"field": "type",
"operator": "starts_with",
"value": "payment."
}
]
}Order and Fulfillment Events
{
"name": "Order Events",
"logic": "OR",
"conditions": [
{
"field": "type",
"operator": "starts_with",
"value": "order."
}
]
}Completed Payments Only
{
"name": "Completed Payments",
"logic": "AND",
"conditions": [
{
"field": "type",
"operator": "equals",
"value": "payment.completed"
}
]
}High-Value Payments ($100+)
{
"name": "High Value ($100+)",
"logic": "AND",
"conditions": [
{
"field": "type",
"operator": "equals",
"value": "payment.completed"
},
{
"field": "data.object.payment.amount_money.amount",
"operator": "greater_than",
"value": "10000"
}
]
}Specific Merchant
{
"name": "Main Store Only",
"logic": "AND",
"conditions": [
{
"field": "merchant_id",
"operator": "equals",
"value": "MLF2Z4SFOAXBM"
}
]
}Headers
Square includes the following headers with webhook notifications:
| Header | Description | Example |
|---|---|---|
x-square-hmacsha256-signature | HMAC-SHA256 signature (base64-encoded) | GF4YkfnJpUKBl7AoP9ND9g== |
content-type | Always application/json | application/json |
user-agent | Square webhook user agent | Square-Webhooks |
x-square-event-id | Unique event identifier | d9b80b48-b8a0-4cf6-96a8-2b7e8a5e3c1d |
Hookbase does not forward these to your destination. A delivery is a fresh request with
Hookbase's own X-Delivery-ID and X-Event-ID plus the headers and auth you configured on the
destination — nothing Square sent reaches your handler as a header. See
what headers a delivery carries.
Testing
Using Square Sandbox
Square provides a sandbox environment for testing:
- Create a separate Hookbase source for sandbox:
curl -X POST https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Square Sandbox",
"slug": "square-sandbox"
}'- In the Square Developer Dashboard, create a webhook subscription pointing to your sandbox source URL
- Use the Square Sandbox test account to trigger events (create payments, orders, etc.)
Best Practices
-
Separate sandbox and production: Use different Hookbase sources for sandbox and production webhook subscriptions
-
Handle idempotency: Use
event_idto prevent duplicate processing, as Square may redeliver events -
Monitor signature failures: Failed signature verifications may indicate a mismatch between the notification URL registered in Square and the one Hookbase uses, or an incorrect signature key
-
Track merchant context: Square payloads include
merchant_idwhich is useful for multi-location or multi-merchant setups -
Handle retries: Square retries failed webhook deliveries with exponential backoff; ensure your handlers are idempotent
-
Use event versions: Some Square events include version numbers on objects -- use these to handle out-of-order delivery
Troubleshooting
Signature Verification Failed
- Verify you are using the Signature key from the webhook subscription, not your application API key
- Ensure the notification URL registered in Square exactly matches your Hookbase webhook URL -- Square includes the URL in the signed content
- Check that the signature key corresponds to the correct environment (sandbox vs production)
- Confirm the source's
providerissquare— a source without one verifies ascustom, which Square's header never satisfies
Missing Events
- Check which events are subscribed in the Square Developer Dashboard under your webhook subscription
- Verify your filters are not blocking expected events
- Check the Square Developer Dashboard webhook logs for delivery attempts and errors
- Ensure your webhook subscription status is Active
Duplicate Events
- Square may retry failed deliveries up to several times with exponential backoff
- Implement idempotency using the
event_idfield from the payload - Check for multiple webhook subscriptions pointing to the same or similar endpoints
URL Mismatch Errors
- Square computes signatures using the notification URL + request body
- If you change your Hookbase webhook URL, you must update the subscription in the Square Developer Dashboard
- Trailing slashes matter -- ensure the URL matches exactly