# Test in sandbox

Build the whole flow against Safaricom's sandbox with test credentials — including the failure paths — before a shilling moves.

## Get sandbox credentials

Create an app on the [Safaricom Daraja portal](https://developer.safaricom.co.ke) and open its **Sandbox** credentials. You want four values:

| Field | Sandbox value |
| --- | --- |
| Consumer key | From the Daraja portal |
| Consumer secret | From the Daraja portal |
| Shortcode | `174379` (Safaricom's shared test paybill) |
| Passkey | From "Lipa na M-Pesa Online" in the portal |

Paste them into your application's **Sandbox** environment in paylod, then mint an `mp_test_…` key.

## What sandbox actually does

Sandbox is Safaricom's own environment, not a paylod mock. The STK Push is real: it goes to Daraja, Daraja answers, and the callback comes back to paylod's hosted receiver exactly as it would in production. What is *not* real is the money — nothing settles.

That means the shape of everything you build — the `202`, the `paymentId`, the webhook envelope, the signature, the result codes — is identical in production. There is no second integration to write.

## Send a test payment

Set `PAYLOD_API_KEY` to the `mp_test_…` key. Nothing else changes.

```ts title="sandbox.ts"
import { Paylod } from "@paylod/node";

const paylod = new Paylod(process.env.PAYLOD_API_KEY!);   // mp_test_… key

const outcome = await paylod.collectAndWait({
  amount: 10,
  phone: "254708374149",     // Safaricom's published test MSISDN
  accountReference: "TEST-1",
});

console.log(outcome.paid ? outcome.receipt : outcome.message);
```

Use a real number you control and you will get a real prompt, with no real money attached.

## Exercise the failure paths

Most production incidents are failure paths nobody tried. Before you go live, make sure your code does something sensible for each of these:

| Result code | How to trigger it in sandbox | What your code should do |
| --- | --- | --- |
| `1032` | Press **Cancel** on the prompt | Show a friendly retry, do not fulfil |
| `1037` | Ignore the prompt until it expires | Retry, or fall back to polling |
| `2001` | Enter the wrong M-Pesa PIN | Ask them to try again |
| `1` | Push an amount above the test balance | Tell the customer the balance is low |

Every code, with its cause and fix, is in the [error reference](/docs/errors).

## Test your webhook locally

No tunnel needed. The [CLI](/docs/cli) streams your live webhook events to `localhost` with the signature intact:

```bash title="Terminal"
npx @paylod/cli listen --forward http://localhost:3000/webhook
```

Your handler verifies a real signature against a real payload, on your laptop. See [Handle the result](/docs/guides/handle-the-result).

## Next

[Go live](/docs/guides/go-live) — swap the credentials, swap the key, keep the code.
