CX Pay API

How It Works

The payment lifecycle from checkout to cash — big picture first, then details.

Big Picture

Every payment flows through three objects:

ObjectWhat it isYou create it?
Checkout SessionA payment page for your customerYes — POST /checkout-sessions
Payment IntentTracks the payment through its lifecycleAuto-created with the session
ChargeThe actual money movement (auth, capture, settle)Auto-created when customer pays

You only interact with the first one. We handle the rest.

Your Server                 CX Pay                        Customer
──────────                  ─────                        ────────
POST /checkout-sessions ──▶ Creates CS + PI
                        ◀── { id, url, payment_intent }

Redirect ──────────────────────────────────────────────▶ Sees hosted checkout
                                                         Enters card details
                                                         3DS challenge (if needed)

                            Charge created ──────────▶   Payment processed
                            PI → succeeded

webhook: payment_intent.succeeded ◀──── CX Pay POST to your endpoint

Fulfill the order ✓

Payment Intent Lifecycle

The Payment Intent is the source of truth for "did they pay?" Here are the states you'll see:

                    ┌──────────────────┐
                    │ requires_payment │  ← just created
                    │     _method      │
                    └────────┬─────────┘
                             │ customer submits card

                    ┌──────────────────┐
                    │   processing     │  ← payment in flight
                    └────────┬─────────┘

                    ┌────────┴─────────┐
                    ▼                  ▼
           ┌──────────────┐   ┌──────────────┐
           │  succeeded   │   │    failed     │
           └──────────────┘   └──────────────┘

The states that matter to you:

StatusWhat it meansWhat to do
succeededMoney captured. Done.Fulfill the order
processingPayment submitted, waiting on networkWait for webhook
requires_actionCustomer needs to complete 3DSNothing — we handle it
canceledCustomer or session expiredShow "payment canceled"
expiredSession timed outOffer to try again

Payment Intent vs Charge

  • Payment Intent = the intention to collect money. Tracks the full lifecycle.
  • Charge = the actual money movement. Created when the payment is attempted.

A Payment Intent can have multiple Charges (e.g., if the first attempt fails and the customer retries). You almost never need to think about Charges — just check the Payment Intent status.

Rule of thumb: Check payment_intent.status to know if you got paid. Ignore Charges unless you're debugging.

Checkout Session vs Payment Intent

"When do I use which?"

  • Checkout Session = what you create. It's a wrapper that generates a hosted payment page.
  • Payment Intent = what you check. The payment_intent ID from the session response is your reference for payment status.
Checkout Session (cs_...)
  └── owns → Payment Intent (pi_...)
                └── owns → Charge (ch_...)

You create the session. You check the intent. You ignore the charge. Simple.

On this page