Square payment failed notifications on your phone.

Get a High-priority push notification when a Square payment fails, such as a declined card or a bounced ACH transfer, with the reason when Square gives one.

How it works

Three steps to your first push

STEP 01

Install the recipe

One click in Studio gives you a personal webhook URL.

STEP 02

Paste your URL into Square

Add it as a webhook for payment.updated.

STEP 03

Get a push on your phone

With the text, sound and buttons from the recipe.

Setup

How to set up the Square payment.updated webhook

  1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL.
  2. In the Square Developer Console (developer.squareup.com/apps), open your application (create a free one if you have none) and switch to Production.
  3. Under Webhooks, choose Subscriptions and click Add subscription.
  4. Paste your webhook URL as the notification URL, tick the payment.updated event, and save.https://••••••••/•••••••• your personal URL, shown after install
  5. Tick **payment.updated**, and also **payment.created** if you take card payments through your own checkout or the Square API: a card that's declined there can arrive as a payment that is already failed when it's created. ACH bank transfers start as pending and fail later, through payment.updated. The recipe ignores every payment that isn't failed, so other payment events stay quiet.
  6. Sandbox deliveries are marked **(sandbox)**.
  7. Square signs every notification with an `x-square-hmacsha256-signature` header (an HMAC-SHA256 of your notification URL and the body, keyed with the subscription's signature key), but JustPush doesn't check that signature yet, so keep your endpoint URL private.
The code

What runs when the webhook arrives

Studio calls handleRequest(request) with the incoming webhook and sends the message it returns. It's yours after install; change anything.

transform.js
1// Square → payment.updated (payment failed)
2// Fires when a payment ends in FAILED: a declined card, or an ACH bank transfer that
3// didn't clear.
4// Docs: https://developer.squareup.com/docs/payments-api/webhooks
5
6// Square doesn't send an event-name header; the event type is in the body's "type".
7// A declined card can arrive as payment.created (already FAILED); an ACH transfer that
8// bounces arrives as payment.updated.
9const EVENTS = ["payment.updated", "payment.created"]
10
11// Square error codes on card_details.errors / bank_account_details.errors, in plain words.
12const REASONS = {
13 GENERIC_DECLINE: "Card declined",
14 CARD_DECLINED: "Card declined",
15 CARD_DECLINED_CALL_ISSUER: "Declined, the customer should call their bank",
16 CARD_DECLINED_VERIFICATION_REQUIRED: "Declined, verification required",
17 INSUFFICIENT_FUNDS: "Insufficient funds",
18 CVV_FAILURE: "Wrong CVV",
19 VERIFY_CVV_FAILURE: "Wrong CVV",
20 ADDRESS_VERIFICATION_FAILURE: "Address check failed",
21 CARD_EXPIRED: "Card expired",
22 INVALID_EXPIRATION: "Invalid expiry date",
23 BAD_EXPIRATION: "Invalid expiry date",
24 EXPIRATION_FAILURE: "Invalid expiry date",
25 INVALID_CARD: "Invalid card",
26 INVALID_CARD_DATA: "Invalid card",
27 TRANSACTION_LIMIT: "Over the card's transaction limit",
28 PAYMENT_LIMIT_EXCEEDED: "Over Square's payment limit",
29 CARDHOLDER_INSUFFICIENT_PERMISSIONS: "Card not allowed for this purchase",
30 READER_DECLINED: "Declined by the card reader",
31}
32
33// Friendlier names for Square's card brands and payment sources.
34const BRANDS = {
35 VISA: "Visa",
36 MASTERCARD: "Mastercard",
37 AMERICAN_EXPRESS: "American Express",
38 DISCOVER: "Discover",
39 DISCOVER_DINERS: "Diners Club",
40 JCB: "JCB",
41 CHINA_UNIONPAY: "UnionPay",
42 SQUARE_GIFT_CARD: "Square gift card",
43 INTERAC: "Interac",
44 EFTPOS: "eftpos",
45 FELICA: "FeliCa",
46 EBT: "EBT",
47}
48
49const WALLETS = {
50 CASH_APP: "Cash App Pay",
51 PAYPAY: "PayPay",
52 ALIPAY: "Alipay",
53 RAKUTEN_PAY: "Rakuten Pay",
54 AU_PAY: "au PAY",
55 D_BARAI: "d払い",
56 MERPAY: "Merpay",
57 WECHAT_PAY: "WeChat Pay",
58 LIGHTNING: "Bitcoin Lightning",
59}
60
61const BNPL = {
62 AFTERPAY: "Afterpay",
63 CLEARPAY: "Clearpay",
64}
65
66const SOURCES = {
67 CASH: "cash",
68 BANK_ACCOUNT: "bank transfer",
69 SQUARE_ACCOUNT: "Square account",
70 EXTERNAL: "an external method",
71}
72
73// Header names can arrive in any case.
74function header(request, name) {
75 const headers = request.headers || {}
76 const key = Object.keys(headers).find((k) => k.toLowerCase() === name)
77 const value = key ? headers[key] : null
78 return Array.isArray(value) ? value[0] : value
79}
80
81// Table lookup that ignores inherited names such as "toString".
82function pick(table, key, fallback) {
83 return typeof key === "string" && Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback
84}
85
86// Square Money is { amount, currency } with amount in the currency's smallest unit
87// (cents for USD/EUR/GBP, whole yen for JPY). Returns null when there's no amount.
88function money(value) {
89 const amount = value?.amount
90 const currency = value?.currency
91
92 if (typeof amount !== "number" || !Number.isFinite(amount) || typeof currency !== "string" || !currency) return null
93
94 try {
95 const format = new Intl.NumberFormat("en-GB", { style: "currency", currency })
96 return format.format(amount / 10 ** format.resolvedOptions().maximumFractionDigits)
97 } catch {
98 return null
99 }
100}
101
102// "via Visa •••• 1111", "via cash", "via Cash App Pay" …
103function method(payment) {
104 if (payment.source_type === "CARD") {
105 const card = payment.card_details?.card ?? {}
106 return pick(BRANDS, card.card_brand, "card") + (card.last_4 ? ` •••• ${card.last_4}` : "")
107 }
108 if (payment.source_type === "WALLET") return pick(WALLETS, payment.wallet_details?.brand, "a digital wallet")
109 if (payment.source_type === "BUY_NOW_PAY_LATER") return pick(BNPL, payment.buy_now_pay_later_details?.brand, "buy now, pay later")
110 return pick(SOURCES, payment.source_type, null)
111}
112
113// The first error code Square attached to the payment, if any.
114function reason(payment) {
115 const errors = payment.card_details?.errors ?? payment.bank_account_details?.errors ?? payment.buy_now_pay_later_details?.errors
116 const code = Array.isArray(errors) ? errors.find((e) => typeof e?.code === "string")?.code : null
117 if (!code) return null
118 const text = code.toLowerCase().replace(/_/g, " ")
119 return pick(REASONS, code, text.charAt(0).toUpperCase() + text.slice(1))
120}
121
122function handleRequest(request) {
123 const body = request.body && typeof request.body === "object" ? request.body : {}
124 const payment = body.data?.object?.payment
125
126 // Guard: only payment events that carry the payment object.
127 if (!EVENTS.includes(body.type) || !payment || typeof payment !== "object") {
128 console.log(`Ignoring Square event "${typeof body.type === "string" ? body.type : "unknown"}"`)
129 return null
130 }
131
132 // payment.updated fires for every change; only a failed payment is worth this push.
133 if (payment.status !== "FAILED") {
134 console.log(`Skipping payment with status "${payment.status ?? "unknown"}"`)
135 return null
136 }
137
138 const amount = money(payment.total_money) ?? money(payment.amount_money) ?? "A payment"
139 const via = method(payment)
140 const why = reason(payment)
141 const sandbox = String(header(request, "square-environment") ?? "").toLowerCase() === "sandbox" ? " (sandbox)" : ""
142
143 return {
144 title: "🚫 Payment failed",
145 message:
146 amount +
147 (via ? ` via ${via}` : "") +
148 (payment.buyer_email_address ? ` from ${payment.buyer_email_address}` : "") +
149 (why ? `. ${why}.` : "") +
150 sandbox,
151 topic: "Square",
152 priority: 1, // High — a customer tried to pay and couldn't
153 }
154}
Payload

The Square payment.updated webhook

This is what Square sends to your URL for payment.updated. It is a sample, trimmed to the fields recipes use.

payment.updated · sample.json
1{
2 "data": {
3 "id": "R2B3Z8WMVt3EAmzYWLZvz7Y69EbZY",
4 "type": "payment",
5 "object": {
6 "payment": {
7 "id": "R2B3Z8WMVt3EAmzYWLZvz7Y69EbZY",
8 "status": "FAILED",
9 "order_id": "pRsjRTgFWATl7so6DxdKBJa7ssbZY",
10 "created_at": "2026-09-28T09:29:47.512Z",
11 "updated_at": "2026-09-28T09:29:48.001Z",
12 "location_id": "S8GWD5R9QB376",
13 "source_type": "CARD",
14 "total_money": {
15 "amount": 12900,
16 "currency": "USD"
17 },
18 "amount_money": {
19 "amount": 12900,
20 "currency": "USD"
21 },
22 "card_details": {
23 "card": {
24 "bin": "411111",
25 "last_4": "1111",
26 "exp_year": 2028,
27 "card_type": "DEBIT",
28 "exp_month": 3,
29 "card_brand": "VISA",
30 "prepaid_type": "NOT_PREPAID"
31 },
32 "errors": [
33 {
34 "code": "INSUFFICIENT_FUNDS",
35 "detail": "Authorization error: 'INSUFFICIENT_FUNDS'",
36 "category": "PAYMENT_METHOD_ERROR"
37 }
38 ],
39 "status": "FAILED",
40 "avs_status": "AVS_ACCEPTED",
41 "cvv_status": "CVV_ACCEPTED",
42 "entry_method": "KEYED"
43 },
44 "version_token": "H8Vnk5Z11SKcueuRti79jGpszSEsSVdhKRrSKCOzILG6o",
45 "buyer_email_address": "[email protected]"
46 }
47 }
48 },
49 "type": "payment.updated",
50 "event_id": "0c3d8a9e-4f71-4b6e-9d2a-7e5b1c9f3a10",
51 "created_at": "2026-09-28T09:29:48.120Z",
52 "merchant_id": "6SSW7HV8K2ST5"
53}
FAQ

Square payment failed notifications: questions

Does this work on iPhone and Android?

Yes. Install the JustPush app from the App Store or Google Play and sign in. Every phone signed in to your account gets the notification.

Do I need to write code?

No. Install the recipe in Studio, paste your webhook URL into Square and you are done. The code is there if you want to change the text, the sound or the buttons.

Can I change what the notification says?

Yes. After install the recipe's code is yours. Edit it in Studio and test it against the sample payload before you save.

What does it cost?

JustPush is free for 30 days. After that it's $19.99 a year, or $39.99 once. There is no extra charge for recipes.

Ready when you are

Square on your phone in two minutes.

Install the recipe, paste one URL, done. Free for 30 days, no credit card required.