Webhooks for Cancelled Terminal Payment

We have an issue with a merchant where the end consumer has invoked a payment, but later cancelled the payment, but we have received APPROVED status webhooks after the cancellation as well.

Order ID: QB9nQiFijAOkmcDHOdM9BJpWs9JZY
Location ID: LVTX8NQD7J2DW

Sequence of events:

“Wed Nov 06 21:07:53 GMT+5:30 2024” : “PAYMENT_CREATED” → We add this. Not from WH.
“Wed Nov 06 21:08:15 GMT+5:30 2024” : “PAYMENT_APPROVED” → From WH.
“Wed Nov 06 21:08:17 GMT+5:30 2024” : “PAYMENT_APPROVED” → From WH.
“Wed Nov 06 21:08:28 GMT+5:30 2024” : “Sale Failed - CANCELED” → From WH.
“Wed Nov 06 21:08:28 GMT+5:30 2024” : “PAYMENT_FAILED” → We add this. Not from WH.
“Wed Nov 06 21:08:39 GMT+5:30 2024” : “PAYMENT_APPROVED” → From WH.
“Wed Nov 06 21:08:40 GMT+5:30 2024” : “PAYMENT_APPROVED” → From WH.
“Wed Nov 06 21:08:41 GMT+5:30 2024” : “Sale Failed - CANCELED” → From WH.
“Wed Nov 06 21:08:52 GMT+5:30 2024” : “Sale Failed - CANCELED” → From WH.
“Wed Nov 06 21:08:52 GMT+5:30 2024” : “PAYMENT_FAILED” → We add this. Not from WH.

Checkout Request Log:

2024-11-06 15:37:07 - SQUARE-TERMINAL API >>>>>>>>>> 
URI         : https://connect.squareup.com/v2/terminals/checkouts
Method      : POST
Headers     : {Accept=[application/json], Content-Type=[application/json], Authorization=[Bearer REDACTED], Square-Version=[2024-01-18], Content-Length=[315]}
Request body: {"idempotency_key":"SQT-a508359f33bca1-1730907427787","checkout":{"amount_money":{"amount":587,"currency":"USD"},"tip_money":{"amount":0,"currency":"USD"},"order_id":"QB9nQiFijAOkmcDHOdM9BJpWs9JZY","device_options":{"device_id":"846CS108A2000949","tip_settings":{"allow_tipping":false},"skip_receipt_screen":true}}}
2024-11-06 15:37:08 - SQUARE-TERMINAL API API  <<<<<<<<<< Status = [200] path = [https://connect.squareup.com/v2/terminals/checkouts]
Status code  : 200
Status text  : OK
Headers      : {Date=[Wed, 06 Nov 2024 15:37:08 GMT], Content-Type=[application/json], Connection=[keep-alive], CF-Ray=[8de629bfac9929bc-IAD], CF-Cache-Status=[DYNAMIC], Strict-Transport-Security=[max-age=631152000; includeSubDomains; preload], Vary=[Accept-Encoding], frame-options=[DENY], square-version=[2024-01-18], squareup--connect--v2--common--versionmetadata-bin=[CgoyMDI0LTAxLTE4], x-content-type-options=[nosniff], x-envoy-decorator-operation=[/v2/terminals/**], x-frame-options=[DENY], x-sq-dc=[aws], x-sq-region=[us-east-1], x-xss-protection=[1; mode=block], Server=[cloudflare]}
Response body: {"checkout": {"id": "qigAOSg61qhqO","amount_money": {"amount": 587,"currency": "USD"},"device_options": {"device_id": "846CS108A2000949","collect_signature": false,"tip_settings": {"allow_tipping": false},"skip_receipt_screen": true,"show_itemized_cart": true},"status": "PENDING","created_at": "2024-11-06T15:37:08.220Z","updated_at": "2024-11-06T15:37:08.220Z","app_id": "sq0idp-TpHen_F96miOUr07zsUKTw","order_id": "QB9nQiFijAOkmcDHOdM9BJpWs9JZY","location_id": "LVTX8NQD7J2DW","payment_type": "CARD_PRESENT","payment_options": {"autocomplete": true},"tip_money": {"amount": 0,"currency": "USD"}}}

Our concern is, can a cancellation happen even after a payment is approved? Also, for a cancelled payment, can any previous approved webhooks be received after?

Yes, this can happen if the customer pays and the checkout is canceled during the payment. The payment will succeed but the request to cancel will fail. Also the cancel webhook is a notification of a request to cancel the checkout. It’s not a notification that the checkout has been canceled. :slightly_smiling_face:

Hello,

It’s fascinating how payment gateways like Square can sometimes send conflicting webhooks, especially with timing delays or retries. It really makes you think about how these systems handle race conditions and the importance of syncing up events properly. It’s interesting that a cancellation can follow an approval, but it also shows how critical it is to double-check the final status of a payment before taking any further action.

We’re constantly working to improve our features based on feedback like this, so I’ll be sure to share your request to the API product team. :slightly_smiling_face: