Skip to main content

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 merchantReference twice does not create two transactions — the API returns the result of the original transaction.
  • Different merchantReference values = 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-Key HTTP header. Use the body field.

When To Retry

OutcomeRetry?
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 timeoutYes, with the same merchantReference.
429 Too Many RequestsYes, with backoff. See Rate Limiting.
5xx server errorYes, with backoff. Then poll for status.

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 merchantReference on 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, or 1103 automatically. These are terminal client errors. Fix the cause and resubmit with a new reference.

Last updated: April 2026