Guides/Your first payment in ten minutes
Guide
Your first payment in ten minutes
Create an app, get a token, send a payment prompt and receive the result on your callback URL.
This walks through one real payment, end to end, against the sandbox. No money moves and nothing here needs a backend of your own: the sandbox answers every call itself.
What you need
- An account. Sign up and Console creates your organization on your first visit.
- An app with the Payments group added. Create one from Console, Apps, or from the Payments group page.
- A place to receive the callback. If the webhook inspector is on, your app's inspector address works; otherwise
any URL your server can answer with
200will do.
Get a token
Every sandbox call carries a bearer token. Exchange your app's consumer key and secret for one with HTTP Basic auth:
curl -X POST 'http://localhost:3000/api/sandbox/oauth/v1/token' \
-u "$LANGO_KEY:$LANGO_SECRET" \
-d 'grant_type=client_credentials'
The answer holds an access_token that lasts one hour. There is no refresh: when it expires, ask for a new one.
{ "access_token": "lgt_4k8w…", "token_type": "Bearer", "expires_in": 3600 }
Send the payment prompt
POST /collect/v1/push starts a payment. The response only confirms the prompt was sent; the final result arrives
on callbackUrl a few seconds later.
curl -X POST 'http://localhost:3000/api/sandbox/collect/v1/push' \
-H "Authorization: Bearer $LANGO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"shortCode": "600100",
"amount": 1500,
"currency": "KES",
"phoneNumber": "254712345678",
"reference": "INV-2041",
"callbackUrl": "https://your-server.example/hooks/lango"
}'
You get back a checkoutId and status: PENDING with a 202. Nothing has been paid yet.
Choosing an outcome with a magic value
The sandbox reads amount and picks a scenario:
amount |
What happens |
|---|---|
| anything else | The customer pays. A callback arrives about 4 seconds later. |
1 |
Insufficient funds. Callback arrives, resultCode: 1001. |
2 |
The customer cancels the prompt. resultCode: 1032. |
3 |
The customer never responds. resultCode: 1037, after about 9 seconds. |
Send phoneNumber: "254700000000" instead and the call itself fails with 400 INVALID_PHONE_NUMBER, no callback at
all. The full list, and how to force a scenario with the X-Lango-Scenario header instead of a magic value, is on
the sandbox page.
Receive the callback
The final result is a POST to the callbackUrl you sent, signed the way described in
Verifying callback signatures:
{
"checkoutId": "chk_8f2k1m9x",
"reference": "INV-2041",
"resultCode": 0,
"resultDescription": "Payment received.",
"amount": 1500,
"receipt": "RCT_9d3ks7",
"phoneNumber": "254712345678",
"paidAt": "2026-01-15T09:30:04Z"
}
resultCode: 0 means it was paid. Anything else is a failure; resultDescription says why.
Check on it instead of waiting
If a callback has not arrived and you need the current state, GET /collect/v1/push/{checkoutId} returns it:
curl 'http://localhost:3000/api/sandbox/collect/v1/push/chk_8f2k1m9x' \
-H "Authorization: Bearer $LANGO_TOKEN"
Next
- Verifying callback signatures before you trust a callback body.
- Moving from sandbox to production once this works end to end.
- The Collect reference has every field, every error and every scenario for this API.