Your First Payment
This is the one test to run before any other. It takes a single mobile money payment all the way to COMPLETED and proves four things at once: your credentials work, you can reach the API, your request shape is accepted, and you can read a terminal state back.
Everything afterwards is a variation on these calls. Get this working first — a failure here is almost always credentials or request shape, and is far easier to diagnose on the happy path than inside a scenario you are also trying to trigger.
The example uses Kenya as the country and M-Pesa as the mobile network operator, which appear in the requests as currency KES and provider mpesa. Substitute your own market from the market reference if you prefer; the steps do not change.
This page is a test procedure. For field-by-field reference — every optional field, every response shape, the full operator list — see Direct API: Mobile Money.
Step 1 — Get an access token
POST /v1/auth/oauth2/token — API reference
Pass: a 200 with an access_token. Tokens last 24 hours and there is no refresh token — request a new one when it expires.
If this fails: a 401 with {"error": "Invalid client credentials"} means the client_id or client_secret is wrong, and the two are deliberately indistinguishable so you cannot tell which. A 400 usually means the body was form-encoded; it must be JSON. See Authentication.
Step 2 — Create a checkout session
POST /v1/checkout/session — API reference
Pass: a 201 with a sessionId. Keep it — the next call needs it.
Two things worth getting right the first time, because they cause most early failures:
amountis in minor units.250000KES is KSh 2,500.00, butUGX,XAFandXOFhave no minor unit, so the same250000is 250,000 whole units.payerDetails.locationis ISO 3166 alpha-3:KEN, notKE. It decides which operators are valid and normalises the mobile number.
Step 3 — Initiate the payment
POST /v1/payment — API reference
Pass: a transactionId and a status of PENDING.
0712345678 is the number from step 2, written in local format. Local format and E.164 both work, and the trigger numbers use local format too.
It isn’t a trigger number, so this payment will succeed. That’s what you want from a first test.
If this fails: check that paymentMethodId is "mobilemoney"; the operator goes in paymentDetails.provider. A 409 Conflict means the session already has an active transaction, so create a new session.
Step 4 — Poll to a terminal state
GET /v1/payment/{transactionId} — API reference
The first response is usually AUTH_REQUIRED. For a real customer, this is while the prompt is on their phone:
Keep polling. The status stays AUTH_REQUIRED for about 10 seconds while CrissCross checks the payment with the operator. Poll every 2 seconds for the first 30 seconds, then every 5–10 seconds, and stop after about 3 minutes. Once it completes:
Pass: status reaches COMPLETED and a processorReference is present.
AUTH_REQUIRED is the normal waiting state for mobile money, not an error. Show it in your UI as “check your phone”.
Stop polling at COMPLETED, FAILED, CANCELLED or EXPIRED. For ERRORED, check authState.canPoll: if it’s true, only the status check failed and the payment may still complete, so keep polling. Otherwise, stop.
What you have just proved
You’ve now proved the happy path. The rest of this section covers failures and the other approval flows.
If you are using Hosted Checkout
Steps 1 and 2 are the same, except you set integrationType to "hosted" on the session. The response includes a paymentLink. Open it and pay on the hosted page instead of calling POST /v1/payment in step 3.
Because you didn’t create the payment yourself, you don’t have its transactionId. Get it from the session once the payment has started:
GET /v1/checkout/session — API reference
Take activeTransaction.transactionId from the response and poll it as in step 4. In production, you’d use your webhook for the final status instead of polling.
Next
- Set up a webhook endpoint and re-run this test. Webhooks are the source of truth for a payment’s final status; polling just keeps your UI up to date. See Testing Webhooks for how to inspect and resend deliveries.
- Work through the approval flow your markets use. Most mobile money operators use the handset prompt you just saw, but some use an in-checkout OTP and some redirect the customer to the operator’s page.
- Then vary the number to produce failures, using the trigger tables.