Payments
Payment status
How to read a payment's status, poll responsibly and treat final states.
Webhooks are the fastest way to learn a result, but networks fail. Always be able to read the status yourself.
| Product | Read endpoint | Final when |
|---|---|---|
| Mobile money push | GET /v1/collections/{public_reference} | succeeded, failed, reversed, refunded |
| Checkout session | GET /v1/checkout-sessions/{id} | paid, expired |
| Control Number | GET /v1/control-numbers/{id} | Each payment's status is verified |
| Payout | GET /v1/disbursements/{reference} | succeeded, failed, reversed |
Polling pattern
While a customer is approving a prompt, poll every 3 to 5 seconds for up to about 3 minutes, then fall back to the webhook and a background check.
async function waitForPayment(reference: string) {
for (let attempt = 0; attempt < 45; attempt++) {
const response = await fetch(`https://api.teepay.me/v1/collections/${reference}`, {
headers: { Authorization: `Bearer ${process.env.TEEPAY_API_KEY}` },
});
if (response.status === 429 || response.status === 503) {
await sleep(Number(response.headers.get('Retry-After') ?? 5) * 1000);
continue;
}
const payment = await response.json();
if (['succeeded', 'failed', 'reversed', 'refunded'].includes(payment.status)) return payment;
await sleep(4000);
}
return null; // still pending: rely on the webhook and reconcile later
}Reconciliation
Once a day, list your collections and compare them with your orders:
curl "https://api.teepay.me/v1/collections?status=succeeded&limit=100" \
-H "Authorization: Bearer $TEEPAY_API_KEY"Follow next_cursor until it is null. Each item includes fee and net when known, so you can
match the amount credited to your TeePay balance.