POST /api/v1/payments with type: "mobile".
Idempotency-Key required — requests without it are rejected with 400 IDEMPOTENCY_KEY_REQUIRED. Idempotency.
Returns 201 with status: "pending" and a USSD push to the customer’s phone, or 200 when replaying an existing Idempotency-Key (the replay body has the same shape as GET /api/v1/payments/{id}).
Body
| Field | Required | Notes |
|---|---|---|
amount, currency, type, customer, phone | yes | type must be "mobile". Currencies: TZS, USD, KES, UGX |
customer | yes | firstname, lastname, email (must be a valid email address) |
network | no | Auto-detected from the phone number; validated when provided |
reference, metadata | no | Duplicate live reference → 409 DUPLICATE_REFERENCE |
webhook_url | no | Status events; overrides merchant default |
callback_url | no | Terminal status only; overrides merchant default |
currency is TZS.
Phone formats
Formatting characters (+, spaces, dashes) are stripped. For TZS, all of these normalize to 255712345678: 0712345678, 712345678, 255712345678, +255712345678. After normalization the number must be a valid Tanzanian mobile (255 + 6/7 + 8 digits) — anything else returns 400 VALIDATION_ERROR.
For other currencies (USD, KES, UGX) the number is reduced to digits only and must be 10–15 digits in international format — no country code is assumed or added, so always send the full international number.
Networks
network is optional — the operator is detected automatically from the phone number. When you do send it, the value is case-insensitive (trimmed and lowercased) and must be one of:
network | Mobile money service |
|---|---|
| vodacom | M-Pesa (alias: mpesa) |
| tigo | Tigo Pesa / Mixx (alias: mixx) |
| airtel | Airtel Money |
| halotel | Halopesa |
| ttcl | T-Pesa |
400 VALIDATION_ERROR. Aliases are normalized — the payment is stored and returned with the canonical name (mpesa → vodacom, mixx → tigo).
What the customer sees
A USSD prompt pops up on the customer’s phone within seconds. They enter their wallet PIN and the payment usually completes in seconds. If they decline, the payment moves tofailed/cancelled; if they ignore the prompt, it stays pending and expires roughly 30 minutes after creation.
Error semantics
| HTTP | error_code | Meaning |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | Missing Idempotency-Key header |
| 400 | VALIDATION_ERROR | Missing required fields, or invalid phone/network/currency |
| 400 | PAYMENT_FAILED | Amount below the 500 TZS minimum (TZS mobile payments) |
| 402 | PAYMENT_DECLINED | Payment declined immediately — no charge; details has transaction_id, status: "failed", and reference |
| 409 | DUPLICATE_REFERENCE | A live (pending/processing/completed) payment with this reference exists; details has existing_transaction_id, existing_status, reference. Failed, cancelled, or expired attempts do not block a retry |
| 502 | PROVIDER_UNAVAILABLE / PROVIDER_ERROR | Temporary upstream outage — retry with the same Idempotency-Key |
API reference
Create Mobile Payment
201 response:
reference is always your merchant reference; provider_reference and external_id are upstream payment identifiers. network appears only when you supplied one (canonical name). Empty fields (payment_url, qr_code) are omitted for mobile payments.
