# 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. - Company: Square (https://www.justpush.io/recipes/square) - Event: `payment.updated` (payment.updated) - Tags: Payments & billing, Failed payments & disputes - Install: https://studio.justpush.io/recipes/square/payment-failed - Web page: https://www.justpush.io/recipes/square/payment-failed ## Setup 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 (shown in Studio after install) as the notification URL, tick the payment.updated event, and save. 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. ## Code Studio calls `handleRequest(request)` with the incoming webhook (`{ method, headers, body, query, raw }`) and sends the message object it returns. Returning `null` sends nothing. ```js // Square → payment.updated (payment failed) // Fires when a payment ends in FAILED: a declined card, or an ACH bank transfer that // didn't clear. // Docs: https://developer.squareup.com/docs/payments-api/webhooks // Square doesn't send an event-name header; the event type is in the body's "type". // A declined card can arrive as payment.created (already FAILED); an ACH transfer that // bounces arrives as payment.updated. const EVENTS = ["payment.updated", "payment.created"] // Square error codes on card_details.errors / bank_account_details.errors, in plain words. const REASONS = { GENERIC_DECLINE: "Card declined", CARD_DECLINED: "Card declined", CARD_DECLINED_CALL_ISSUER: "Declined, the customer should call their bank", CARD_DECLINED_VERIFICATION_REQUIRED: "Declined, verification required", INSUFFICIENT_FUNDS: "Insufficient funds", CVV_FAILURE: "Wrong CVV", VERIFY_CVV_FAILURE: "Wrong CVV", ADDRESS_VERIFICATION_FAILURE: "Address check failed", CARD_EXPIRED: "Card expired", INVALID_EXPIRATION: "Invalid expiry date", BAD_EXPIRATION: "Invalid expiry date", EXPIRATION_FAILURE: "Invalid expiry date", INVALID_CARD: "Invalid card", INVALID_CARD_DATA: "Invalid card", TRANSACTION_LIMIT: "Over the card's transaction limit", PAYMENT_LIMIT_EXCEEDED: "Over Square's payment limit", CARDHOLDER_INSUFFICIENT_PERMISSIONS: "Card not allowed for this purchase", READER_DECLINED: "Declined by the card reader", } // Friendlier names for Square's card brands and payment sources. const BRANDS = { VISA: "Visa", MASTERCARD: "Mastercard", AMERICAN_EXPRESS: "American Express", DISCOVER: "Discover", DISCOVER_DINERS: "Diners Club", JCB: "JCB", CHINA_UNIONPAY: "UnionPay", SQUARE_GIFT_CARD: "Square gift card", INTERAC: "Interac", EFTPOS: "eftpos", FELICA: "FeliCa", EBT: "EBT", } const WALLETS = { CASH_APP: "Cash App Pay", PAYPAY: "PayPay", ALIPAY: "Alipay", RAKUTEN_PAY: "Rakuten Pay", AU_PAY: "au PAY", D_BARAI: "d払い", MERPAY: "Merpay", WECHAT_PAY: "WeChat Pay", LIGHTNING: "Bitcoin Lightning", } const BNPL = { AFTERPAY: "Afterpay", CLEARPAY: "Clearpay", } const SOURCES = { CASH: "cash", BANK_ACCOUNT: "bank transfer", SQUARE_ACCOUNT: "Square account", EXTERNAL: "an external method", } // Header names can arrive in any case. function header(request, name) { const headers = request.headers || {} const key = Object.keys(headers).find((k) => k.toLowerCase() === name) const value = key ? headers[key] : null return Array.isArray(value) ? value[0] : value } // Table lookup that ignores inherited names such as "toString". function pick(table, key, fallback) { return typeof key === "string" && Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback } // Square Money is { amount, currency } with amount in the currency's smallest unit // (cents for USD/EUR/GBP, whole yen for JPY). Returns null when there's no amount. function money(value) { const amount = value?.amount const currency = value?.currency if (typeof amount !== "number" || !Number.isFinite(amount) || typeof currency !== "string" || !currency) return null try { const format = new Intl.NumberFormat("en-GB", { style: "currency", currency }) return format.format(amount / 10 ** format.resolvedOptions().maximumFractionDigits) } catch { return null } } // "via Visa •••• 1111", "via cash", "via Cash App Pay" … function method(payment) { if (payment.source_type === "CARD") { const card = payment.card_details?.card ?? {} return pick(BRANDS, card.card_brand, "card") + (card.last_4 ? ` •••• ${card.last_4}` : "") } if (payment.source_type === "WALLET") return pick(WALLETS, payment.wallet_details?.brand, "a digital wallet") if (payment.source_type === "BUY_NOW_PAY_LATER") return pick(BNPL, payment.buy_now_pay_later_details?.brand, "buy now, pay later") return pick(SOURCES, payment.source_type, null) } // The first error code Square attached to the payment, if any. function reason(payment) { const errors = payment.card_details?.errors ?? payment.bank_account_details?.errors ?? payment.buy_now_pay_later_details?.errors const code = Array.isArray(errors) ? errors.find((e) => typeof e?.code === "string")?.code : null if (!code) return null const text = code.toLowerCase().replace(/_/g, " ") return pick(REASONS, code, text.charAt(0).toUpperCase() + text.slice(1)) } function handleRequest(request) { const body = request.body && typeof request.body === "object" ? request.body : {} const payment = body.data?.object?.payment // Guard: only payment events that carry the payment object. if (!EVENTS.includes(body.type) || !payment || typeof payment !== "object") { console.log(`Ignoring Square event "${typeof body.type === "string" ? body.type : "unknown"}"`) return null } // payment.updated fires for every change; only a failed payment is worth this push. if (payment.status !== "FAILED") { console.log(`Skipping payment with status "${payment.status ?? "unknown"}"`) return null } const amount = money(payment.total_money) ?? money(payment.amount_money) ?? "A payment" const via = method(payment) const why = reason(payment) const sandbox = String(header(request, "square-environment") ?? "").toLowerCase() === "sandbox" ? " (sandbox)" : "" return { title: "🚫 Payment failed", message: amount + (via ? ` via ${via}` : "") + (payment.buyer_email_address ? ` from ${payment.buyer_email_address}` : "") + (why ? `. ${why}.` : "") + sandbox, topic: "Square", priority: 1, // High — a customer tried to pay and couldn't } } ``` ## Square payment.updated webhook payload (sample) ```json { "data": { "id": "R2B3Z8WMVt3EAmzYWLZvz7Y69EbZY", "type": "payment", "object": { "payment": { "id": "R2B3Z8WMVt3EAmzYWLZvz7Y69EbZY", "status": "FAILED", "order_id": "pRsjRTgFWATl7so6DxdKBJa7ssbZY", "created_at": "2026-09-28T09:29:47.512Z", "updated_at": "2026-09-28T09:29:48.001Z", "location_id": "S8GWD5R9QB376", "source_type": "CARD", "total_money": { "amount": 12900, "currency": "USD" }, "amount_money": { "amount": 12900, "currency": "USD" }, "card_details": { "card": { "bin": "411111", "last_4": "1111", "exp_year": 2028, "card_type": "DEBIT", "exp_month": 3, "card_brand": "VISA", "prepaid_type": "NOT_PREPAID" }, "errors": [ { "code": "INSUFFICIENT_FUNDS", "detail": "Authorization error: 'INSUFFICIENT_FUNDS'", "category": "PAYMENT_METHOD_ERROR" } ], "status": "FAILED", "avs_status": "AVS_ACCEPTED", "cvv_status": "CVV_ACCEPTED", "entry_method": "KEYED" }, "version_token": "H8Vnk5Z11SKcueuRti79jGpszSEsSVdhKRrSKCOzILG6o", "buyer_email_address": "jane@example.com" } } }, "type": "payment.updated", "event_id": "0c3d8a9e-4f71-4b6e-9d2a-7e5b1c9f3a10", "created_at": "2026-09-28T09:29:48.120Z", "merchant_id": "6SSW7HV8K2ST5" } ``` ## FAQ ### 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.