Webhooks vs polling
When to use webhooks and when to poll the API — the tradeoffs, patterns, and Wajub's recommendation.
Mobile Money payments are asynchronous — a customer initiates a payment and confirms it seconds or minutes later via USSD or a push notification. This means you cannot know the outcome at the moment you create the payment. You have two options to learn the result: webhooks or polling.
Webhooks (recommended)
Wajub sends a signed POST request to your endpoint when the payment status changes.
Your server creates payment → customer pays → Wajub sends payment.succeeded to your endpointAdvantages:
- Near-instant notification (typically < 1 second after confirmation)
- No wasted API calls
- Scales to any volume without rate-limit concerns
- The only way to reliably catch all state changes (failed, expired, refunded)
When to use: Always. Webhooks are the canonical source of truth for payment status.
Polling (fallback pattern)
You call GET /payments/{id} repeatedly until the status is terminal.
Your server creates payment → loop: GET /payments/{id} every N seconds → status = succeededWhen polling is appropriate:
- As a safety net on the browser return URL (always verify server-side on redirect back)
- When building a one-off script or internal tool where webhooks are impractical
- As a fallback if your webhook endpoint was unavailable and you need to reconcile
Caution: Polling more frequently than every 5 seconds will consume rate-limit budget quickly. Do not use polling as the primary fulfillment signal in a production integration.
async function waitForPayment(id: string, timeoutMs = 60_000): Promise<string> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const { transaction } = await wajub.payments.retrieve(id);
const terminal = ['succeeded', 'failed', 'cancelled', 'expired'];
if (terminal.includes(transaction.status)) return transaction.status;
await new Promise(r => setTimeout(r, 5000)); // wait 5s between polls
}
return 'unknown'; // timed out — rely on webhook
}Decision guide
| Scenario | Approach |
|---|---|
| Production fulfillment (deliver order, top up balance) | Webhooks — mandatory |
| Browser return page (confirm before showing "thank you") | Poll once via GET /payments/{id} |
| Reconciliation script (catch missed events) | Poll with cursor via GET /payments?created_after=… |
| Real-time dashboard (show live status) | Webhooks + Pusher/SSE |
Never fulfill based on the browser return alone
The browser return URL (callback) is not reliable — the customer can close the tab, the
redirect can fail, or the URL can be forged. Always verify via webhook or a server-side
GET /payments/{id} before fulfilling an order.
Best of both worlds
The production pattern is: webhooks for fulfillment + one server-side poll on the return page:
- When the customer returns to your
callbackURL, callGET /payments/{id}to get the current status and show the appropriate page (success, pending, failed). - Your webhook handler is the source of truth — it fulfills the order when
payment.succeededarrives, regardless of whether the customer ever returned.
Was this page helpful?