Why Polling Is Needed
Mobile money payments are asynchronous. After calling POST /api/v1/collect, the transaction sits at pending until the user approves (or rejects) the USSD prompt on their phone. This can take 5 seconds to 5 minutes.
Recommended approach: Use webhooks in production. Use polling for quick integrations, dashboards, or when a callback URL is not available.
Get a Single Transaction
GET /api/v1/transactions/{reference}
Authorization: Bearer {api_key}
The reference can be either:
internal_reference (e.g., RHP-2024-ABC123) — returned in the collect response
provider_reference — the ID from the payment provider
Response
{
"success": true,
"data": {
"id": "01j2k3m4n5p6q7r8s9t0uvwx",
"internal_reference": "RHP-2024-ABC123",
"provider_reference": "9876543210",
"status": "successful",
"payment_method": "mobile_money",
"provider": "relworx",
"phone_number": "256700123456",
"amount": 50000,
"currency": "UGX",
"net_amount": 49000,
"commission_amount": 1000,
"environment": "live",
"created_at": "2024-07-15T08:30:00Z",
"updated_at": "2024-07-15T08:31:47Z"
}
}
List Transactions
GET /api/v1/transactions
Authorization: Bearer {api_key}
Returns all transactions for the authenticated project, sorted by created_at descending.
Polling Pattern
Implement polling with exponential backoff to avoid hitting rate limits:
type TransactionStatus = "pending" | "successful" | "failed";
interface Transaction {
internal_reference: string;
status: TransactionStatus;
amount: number;
currency: string;
}
async function pollUntilComplete(
ref: string,
apiKey: string,
maxAttempts = 24,
intervalMs = 5000
): Promise<Transaction> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
if (attempt > 0) {
await new Promise(resolve => setTimeout(resolve, intervalMs));
}
const res = await fetch(`https://api.rohopay.com/api/v1/transactions/${ref}`, {
headers: { "Authorization": `Bearer ${apiKey}` },
});
const { success, data, error } = await res.json();
if (!success) throw new Error(error?.message ?? "Fetch failed");
if (data.status === "successful") return data;
if (data.status === "failed") throw new Error("Payment was rejected or failed");
}
throw new Error(`Payment still pending after ${maxAttempts} attempts`);
}
Payment States
| Status | Meaning | Terminal? |
|---|
pending | Awaiting user USSD approval | No |
successful | User approved; funds received | Yes ✅ |
failed | Declined, expired, or rejected | Yes ❌ |
Once a transaction reaches successful or failed it will never change again.
Typical USSD approval takes 10–60 seconds. Poll every 5 seconds for at most 2 minutes before treating the payment as timed out on your side.