Idempotency & Retries
Network blips, timeouts, and dropped connections are inevitable. The Pagamio VAS API gives you a deterministic way to retry safely without double-charging your customer.
The Idempotency Key: merchantReference
The merchantReference field on POST /purchase is the idempotency key.
- Set a unique value per logical purchase attempt. A UUID, or a deterministic identifier from your system (e.g.
"INV-2026-04-000123"), works well. - Submitting the same
merchantReferencetwice does not create two transactions — the API returns the result of the original transaction. - Different
merchantReferencevalues = different transactions, even if the rest of the payload is identical.
{
"productCode": "AIR-VOD-001",
"amount": 50.0,
"mobileNumber": "+27821234567",
"paymentMethod": "cash",
"channelId": "YOUR_CHANNEL_ID",
"merchantReference": "INV-2026-04-000123"
}
The API does not currently honour an
Idempotency-KeyHTTP header. Use the body field.
When To Retry
| Outcome | Retry? |
|---|---|
200 OK with responseCode: "0" (success) | No — you have a final result. |
200 OK with terminal failure (e.g. 1009, 1103) | No — fix the cause first. |
200 OK with non-terminal status (PENDING/PROCESSING) | No retry of /purchase. Poll instead (see below). |
| Network error / connection reset / read timeout | Yes, with the same merchantReference. |
429 Too Many Requests | Yes, with backoff. See Rate Limiting. |
5xx server error | Yes, with backoff. Then poll for status. |
Recommended Retry Pattern
Exponential backoff with jitter:
attempt = 1
while attempt <= MAX_ATTEMPTS:
response = POST /purchase {merchantReference: REF, ...}
if response is success or terminal failure:
return response
if response is retryable (network error / 429 / 5xx):
delay = min(BASE * 2^(attempt-1), 60s) +/- 25% jitter
sleep(delay)
attempt += 1
continue
return response
# After exhausting retries, do not assume failure. Poll status.
return pollStatus(REF)
Suggested values: BASE = 1s, MAX_ATTEMPTS = 5.
Polling For Final Status
If you ever lose certainty about a transaction's outcome (timeout mid-call, retries exhausted, etc.), do not assume it failed. Query the POST /transactions endpoint, filter by your merchantReference or by transactionId, and use the returned status as the source of truth.
Suggested polling interval: every 5 seconds for the first minute, then every 30 seconds for up to 5 minutes. If the transaction is still not terminal after 5 minutes, raise a support ticket.
Common Mistakes
- Generating a new
merchantReferenceon each retry. This creates duplicate transactions. Use the same reference until you have a terminal answer. - Treating a network timeout as a failure. The transaction may have completed on the server. Poll first.
- Retrying a
1009,1005, or1103automatically. These are terminal client errors. Fix the cause and resubmit with a new reference.
Last updated: April 2026