Dev Portal
Sandbox operational5 APIs5 groups11 operations

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 200 will 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:

bash
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.

json
{ "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.

bash
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:

json
{
  "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:

bash
curl 'http://localhost:3000/api/sandbox/collect/v1/push/chk_8f2k1m9x' \
  -H "Authorization: Bearer $LANGO_TOKEN"

Next