Fired when a hosted-checkout session ended without a member + payment method + subscription being created.
When it fires
Section titled “When it fires”A hosted-checkout session ended in a non-success terminal state. Reasons include:
- The user clicked Cancel on the hosted page.
- The gateway rejected the card during the hosted-form step.
- Provisioning the member / subscription on Topiic’s side errored after card capture.
The event does not fire if the user simply closes the tab — the session sits in AwaitingPayment until it hits exp, at which point it transitions to Expired silently. Use a reconciliation pass to find these (see Handling the return URL).
Headers
Section titled “Headers”Same as checkout.completed: Content-Type, Topiic-Event-Id, Topiic-Idempotency-Key, Topiic-Signature.
{ "id": "9d8c7b6a-5e4f-3a2d-1c0b-9a8f7e6d5c4b", "type": "checkout.failed", "createdAt": "2026-05-27T10:31:48.512Z", "data": { "sessionId": "7e6d5c4b-3a2f-1e0d-9c8b-7a6f5e4d3c2b", "externalRef": "cust_42", "failureReason": "User abandoned checkout." }}data fields
Section titled “data fields”| Field | Type | Notes |
|---|---|---|
sessionId | UUID | The Topiic checkout session id. Look it up in the portal for full audit detail. |
externalRef | string | Whatever you passed as ref in the original signed deep link. |
failureReason | string | Human-readable reason. Not a stable identifier — do not switch on its value. |
What to do on receipt
Section titled “What to do on receipt”- Verify the signature. Reject anything that fails.
- Look up
data.externalRefto find the customer on your side. - Decide what state to put them in. Typically:
- Mark the customer as “subscribe attempt failed” so they show up in a retry queue.
- Do not delete the customer record — they may try again.
- Optionally surface the reason to your support team, but don’t show the raw string to the end user (it’s intended for staff).
- Return 2xx within 10 seconds.
Common failure reasons today
Section titled “Common failure reasons today”failureReason substring | Likely cause |
|---|---|
User abandoned checkout | Customer clicked Cancel or aborted via UI. |
Checkout session expired | Customer didn’t finish before exp. |
Card capture failed | The gateway rejected the card token (declined, expired, fraud, etc). |
Could not provision membership | Internal error after capture — Topiic’s logs will have detail. |
Don’t pattern-match on this string in production logic — Topiic may refine the wording without notice. If you need to branch behaviour by failure type, ask for that to be elevated into a stable field.
Example handler (Node.js)
Section titled “Example handler (Node.js)”async function handleCheckoutFailed(event, db) { const { externalRef, sessionId, failureReason } = event.data;
await db.transaction(async (tx) => { const { rowCount } = await tx.query( 'INSERT INTO topiic_events (event_id) VALUES ($1) ON CONFLICT DO NOTHING', [event.id] ); if (rowCount === 0) return;
await tx.query( `UPDATE customers SET status = 'subscribe_failed', last_failure_reason = $1, last_failed_at = NOW() WHERE external_id = $2`, [failureReason, externalRef] );
await tx.query( `INSERT INTO failed_signups (external_ref, session_id, reason, occurred_at) VALUES ($1, $2, $3, NOW())`, [externalRef, sessionId, failureReason] ); });}