Mobile money status polling
Mobile money settlements are always asynchronous. The customer receives a USSD or in-app prompt; the gateway only knows the result once the carrier confirms.
Use GET /payments/mobile-money/status/{payment_id}
to poll. The endpoint is optimised for live UI: it returns a
progress percentage, a localised message, and an estimated_completion
timestamp.
When to poll
Section titled “When to poll”After /payments/submit returns
status: "pending_mobile_money_verification" (or simply
processing):
1. POST /payments/collect → status: pending2. POST /payments/submit → status: processing ← start polling { mobile_money_phone_number: "..." }3. GET /payments/mobile-money/status/... → status: processing, progress: 254. GET /payments/mobile-money/status/... → status: processing, progress: 505. GET /payments/mobile-money/status/... → status: completed, progress: 100In production, prefer the webhook on notify_url over polling.
Polling is for the UI spinner — webhooks are authoritative.
Polling shape
Section titled “Polling shape”curl https://app.api.gtwy.pdirect.com/api/v1/payments/mobile-money/status/pay_encrypted_abc123 \ -H "Authorization: Bearer sess_..."Response:
{ "status_code": 200, "message": "Status retrieved successfully", "data": { "status": "processing", "progress": 50, "message": "Payment is being processed by the carrier", "payment_info": null, "provider_reference": "onafriq_ref_abc123", "estimated_completion": "2026-05-05T10:17:00Z" }}Fields:
| Field | Notes |
|---|---|
status | One of pending, processing, completed, failed, cancelled. |
progress | 0–100. Approximate. Use it to drive a progress bar. |
message | Localised status. Suitable for “Processing your payment” copy. |
payment_info | Populated only on terminal states (completed, failed). |
provider_reference | Carrier-side ID. Quote when contacting support. |
estimated_completion | When the carrier expects to be done. Use to set polling cadence. |
Cadence
Section titled “Cadence”Poll every 5 seconds with a maximum of 60 attempts (≈ 5 minutes total). That’s what both SDKs do internally.
async function pollUntilTerminal(paymentId: string, sessionToken: string) { for (let i = 0; i < 60; i++) { const r = await fetch( `https://app.api.gtwy.pdirect.com/api/v1/payments/mobile-money/status/${paymentId}`, { headers: { Authorization: `Bearer ${sessionToken}` } }, ); const { data } = await r.json(); if (data.status === "completed" || data.status === "failed" || data.status === "cancelled") { return data; } await new Promise((r) => setTimeout(r, 5000)); } return { status: "timeout" } as const;}For very-long-tail flows (some carriers in some markets can take 30+ minutes), surrender polling and rely on the webhook for the final state.
SDK behaviour
Section titled “SDK behaviour”The Flutter SDK’s PdirectMobileMoneyProgressScreen polls every
5 seconds, max 60 attempts, drives the progress UI, and dispatches
into the PdirectCommonService.paymentScreenStream when terminal.
The Angular SDK’s <pdirect-payment-status> component does the
same, configurable via PdirectPaymentStatusConfig:
const statusConfig: PdirectPaymentStatusConfig = { paymentId: "pay_encrypted_abc123", config: { token: "merchant-app-key", isProduction: true }, pollingInterval: 5000, // ms maxPollingAttempts: 60, autoStart: true,};Fast path
Section titled “Fast path”If the local transaction is already in a terminal state (e.g. you’ve just received the webhook), the gateway returns immediately without calling the carrier — the local DB is the source of truth once the state is final.
Common failures
Section titled “Common failures”error_code | Meaning | Fix |
|---|---|---|
1110 INVALID_FIELD_FORMAT | payment_id < 10 chars | Pass the full encrypted ID |
1204 TRANSACTION_NOT_FOUND | Wrong session or wrong ID | Check payment_id, ensure session is for the same merchant |
1404 EXTERNAL_SERVICE_ERROR | Carrier API down | Back off, retry |
See also
Section titled “See also”- Payment methods —
mobile_moneyfield reference - Webhooks overview — preferred alternative to polling
- Rate limits — polling counts toward
30/min