Skip to content

Square Integration

Receive and route Square webhooks for payments, orders, customers, invoices, and inventory events.

Setup

1. Create a Source in Hookbase

bash
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",
    "verificationConfig": {
      "type": "hmac",
      "secret": "your-square-signature-key",
      "algorithm": "sha256",
      "header": "x-square-hmacsha256-signature",
      "encoding": "base64"
    }
  }'

Save your webhook URL:

https://api.hookbase.app/ingest/{orgSlug}/square

2. Configure Square Webhook

  1. Go to the Square Developer Dashboard
  2. Select your application
  3. Navigate to Webhooks in the left sidebar
  4. Click Add Subscription
  5. Enter your Hookbase webhook URL as the Notification URL
  6. Select the events you want to receive
  7. Click Save
  8. Copy the Signature key from the subscription details
  9. Update your Hookbase source with this signature key

3. Create Routes

bash
# 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", "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", "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_...", "destinationIds": ["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_...", "destinationIds": ["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 automatically verifies this when configured:

json
{
  "verificationConfig": {
    "type": "hmac",
    "secret": "your-square-signature-key",
    "algorithm": "sha256",
    "header": "x-square-hmacsha256-signature",
    "encoding": "base64"
  }
}

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_body

This is different from most webhook providers that sign only the request body. If you are verifying signatures manually, ensure you prepend your Hookbase webhook URL to the raw body before computing the HMAC. Hookbase handles this automatically when you configure the source with the hmac verification type and your Square signature key.

TIP

If signature verification fails after changing your webhook URL, make sure the notification URL registered in Square matches the URL Hookbase uses for verification. The URL is part of the signed content.

Common Events

payment.completed

Triggered when a payment is successfully completed.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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": "[email protected]",
        "phone_number": "+15555551234",
        "preferences": {
          "email_unsubscribed": false
        }
      }
    }
  }
}

inventory.count.updated

Triggered when inventory counts change.

json
{
  "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

javascript
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

javascript
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

javascript
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

json
{
  "name": "Payment Events",
  "logic": "or",
  "conditions": [
    {
      "field": "type",
      "operator": "starts_with",
      "value": "payment."
    }
  ]
}

Order and Fulfillment Events

json
{
  "name": "Order Events",
  "logic": "or",
  "conditions": [
    {
      "field": "type",
      "operator": "starts_with",
      "value": "order."
    }
  ]
}

Completed Payments Only

json
{
  "name": "Completed Payments",
  "logic": "and",
  "conditions": [
    {
      "field": "type",
      "operator": "equals",
      "value": "payment.completed"
    }
  ]
}

High-Value Payments ($100+)

json
{
  "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

json
{
  "name": "Main Store Only",
  "logic": "and",
  "conditions": [
    {
      "field": "merchant_id",
      "operator": "equals",
      "value": "MLF2Z4SFOAXBM"
    }
  ]
}

Headers

Square includes the following headers with webhook notifications:

HeaderDescriptionExample
x-square-hmacsha256-signatureHMAC-SHA256 signature (base64-encoded)GF4YkfnJpUKBl7AoP9ND9g==
content-typeAlways application/jsonapplication/json
user-agentSquare webhook user agentSquare-Webhooks
x-square-event-idUnique event identifierd9b80b48-b8a0-4cf6-96a8-2b7e8a5e3c1d

Testing

Using Square Sandbox

Square provides a sandbox environment for testing:

  1. Create a separate Hookbase source for sandbox:
bash
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",
    "verificationConfig": {
      "type": "hmac",
      "secret": "your-sandbox-signature-key",
      "algorithm": "sha256",
      "header": "x-square-hmacsha256-signature",
      "encoding": "base64"
    }
  }'
  1. In the Square Developer Dashboard, create a webhook subscription pointing to your sandbox source URL
  2. Use the Square Sandbox test account to trigger events (create payments, orders, etc.)

Best Practices

  1. Separate sandbox and production: Use different Hookbase sources for sandbox and production webhook subscriptions

  2. Handle idempotency: Use event_id to prevent duplicate processing, as Square may redeliver events

  3. 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

  4. Track merchant context: Square payloads include merchant_id which is useful for multi-location or multi-merchant setups

  5. Handle retries: Square retries failed webhook deliveries with exponential backoff; ensure your handlers are idempotent

  6. Use event versions: Some Square events include version numbers on objects -- use these to handle out-of-order delivery

Troubleshooting

Signature Verification Failed

  1. Verify you are using the Signature key from the webhook subscription, not your application API key
  2. Ensure the notification URL registered in Square exactly matches your Hookbase webhook URL -- Square includes the URL in the signed content
  3. Check that the signature key corresponds to the correct environment (sandbox vs production)
  4. Make sure the source verificationConfig uses "encoding": "base64" since Square sends base64-encoded signatures

Missing Events

  1. Check which events are subscribed in the Square Developer Dashboard under your webhook subscription
  2. Verify your filters are not blocking expected events
  3. Check the Square Developer Dashboard webhook logs for delivery attempts and errors
  4. Ensure your webhook subscription status is Active

Duplicate Events

  1. Square may retry failed deliveries up to several times with exponential backoff
  2. Implement idempotency using the event_id field from the payload
  3. Check for multiple webhook subscriptions pointing to the same or similar endpoints

URL Mismatch Errors

  1. Square computes signatures using the notification URL + request body
  2. If you change your Hookbase webhook URL, you must update the subscription in the Square Developer Dashboard
  3. Trailing slashes matter -- ensure the URL matches exactly

Released under the MIT License.