> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.crisscross.money/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.crisscross.money/_mcp/server.

# Mobile Money Trigger Numbers

> The complete reference of CrissCross sandbox trigger numbers for mobile money — failures, errors, transient faults, delayed confirmations, OTP and redirect rails.

In the sandbox, the payer's mobile number (`paymentDetails.payerMobileNumber`) decides what happens to a payment. **Any number not listed here succeeds.**

You can send the number in local or international format: in Kenya, `0970000005` and `+254970000005` give the same result. The same numbers work in every market, so set `payerDetails.location` to the country you're testing.

The sandbox also ignores the operator, so every number works the same with any operator. To mirror a market, check the Approval flow column on its [country page](/payment-method-mobile-money#supported-countries) and use the matching section below. Flows where the customer dials a USSD code themselves can't be tested in the sandbox yet.

### Authorisation failures

The transaction ends in `FAILED` with `authState.state: "failed"` and emits `transaction.failed`. Terminal — to retry, start a new transaction.

| Number       | `authState.code`           | Meaning                                                  |
| ------------ | -------------------------- | -------------------------------------------------------- |
| `0970000005` | `payinInsufficientFunds`   | The payer's wallet balance is too low.                   |
| `0970000006` | `customerRejected`         | The payer declined the prompt on their handset.          |
| `0970000007` | `timeout`                  | The payer did not respond before the operator gave up.   |
| `0970000008` | `accountNotActive`         | The wallet exists but is inactive or suspended.          |
| `0970000009` | `payinPayerInvalidAccount` | No wallet exists for this number on the chosen operator. |
| `0970000010` | `payinPayerLimitExceeded`  | The payer's daily limit would be exceeded.               |
| `0970000011` | `monthlyLimitExceeded`     | The payer's monthly limit would be exceeded.             |
| `0970000014` | `transactionNotFound`      | The provider cannot locate the transaction.              |

### System errors

These return `authState.state: "error"` rather than `"failed"` — the payment was not declined, something broke. Check `canRetry` and `canPoll` on the response before deciding what to do.

| Number       | `authState.code`  | Meaning                         |
| ------------ | ----------------- | ------------------------------- |
| `0970000012` | `internalError`   | A CrissCross-side error.        |
| `0970000013` | `downstreamError` | The operator returned an error. |

### Transient faults

These misbehave for the first **2 seconds** after the transaction is created, then behave normally. Use them to prove your retry and polling code recovers rather than giving up.

| Number       | Behaviour                                                                                                                                                   |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0970000015` | `POST /v1/payment` errors with `downstreamError` inside the window, then succeeds on a retry.                                                               |
| `0970000016` | The transaction is created normally, but `GET /v1/payment/{transactionId}` errors with `downstreamError` inside the window before returning a normal state. |
| `0970000017` | The first execution fails. If your account has a fallback processor configured for the same method, the fallback attempt succeeds.                          |

### Delayed confirmations

These stay in `AUTH_REQUIRED` for a fixed delay, then authorise — a payer who is slow to approve. Pair a delay **longer than your session expiry** to exercise the late-debit path: the confirmation lands after the transaction has expired, and the automatic refund fires.

| Number       | Delay      | As a late debit                                                                   |
| ------------ | ---------- | --------------------------------------------------------------------------------- |
| `0970000018` | 5 minutes  | Refund **fails** — `accountNotActive`                                             |
| `0970000019` | 30 seconds | Too close to the 30-second minimum session expiry to exercise late debit reliably |
| `0970000020` | 1 minute   | Refund **fails** — recipient limit reached                                        |
| `0970000026` | 1 minute   | Refund **succeeds** — use this one to test the full path                          |

> **Note**
>
> `0970000018` respects a `SANDBOX_DELAYED_CONFIRMATION_MS` override on the environment, so an operator-run demo may use a shorter window than the 5 minutes shown here.

#### Why some refunds fail

A refund is paid back to the number the payment came from, and it goes through the payout sandbox. There, a number ending in `12`, `14`, `16`, `18`, `20` or `22` fails (see [Payout Trigger Endings](/payout-sandbox-testing)).

That's why the refunds for `0970000018` and `0970000020` fail. To test a late debit and a successful refund end to end, use `0970000026`.

### OTP

Use `0970000021` to test a payment that asks the payer for a one-time code. The response has `authState.authMethodType: "fields"` with one `otp` field.

Send the code with `POST /v1/payment/authorize` ([API reference](/api-reference/collect/payments/payment-initiation/authorize-transaction)):

| Code           | Result                                                                              |
| -------------- | ----------------------------------------------------------------------------------- |
| `123456`       | The payment completes shortly after.                                                |
| Any other code | The payment fails with `invalidPin`. The payer isn't asked again, as in production. |
| No code        | The payment expires with the session.                                               |

If your account has a fallback processor, the fallback attempt fails straight away rather than asking for a second code.

### Redirect

These numbers send the payer to a URL to approve the payment, as some wallets do. The response has `authState.authMethodType: "redirect"`.

| Number       | What the payer sees                                  | Result                           |
| ------------ | ---------------------------------------------------- | -------------------------------- |
| `0970000025` | A test page with **Approve** and **Decline** buttons | Whatever the payer clicks        |
| `0970000023` | The same test page, with `requiresTopLevel: true`    | Whatever the payer clicks        |
| `0970000022` | No page; they go straight to your `redirectUrl`      | Completes after about 10 seconds |
| `0970000024` | No page; they go straight to your `redirectUrl`      | Fails after about 10 seconds     |

* **Test page numbers** only resolve when the payer clicks. If they ignore the page, the payment expires with the session. Use `0970000023` to test wallets that must open outside an iframe.
* **No-page numbers** need no clicks, so use them in automated tests. The result arrives through polling or your webhook.

After the payer decides, Hosted Checkout returns them to the checkout. With the Direct API, they go to the session's `redirectUrl` with `status=complete` or `status=failed`. Always set `redirectUrl`, or the payer ends up on a placeholder page.

As with OTP, a fallback processor fails straight away rather than redirecting the payer a second time.

### Success

Any number not listed here succeeds. The payment goes from `PENDING` to `AUTH_REQUIRED` and reaches `COMPLETED` about 10 seconds after you create it, with a `processorReference`.