> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.epaygames.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.epaygames.io/_mcp/server.

# Idempotency & Safe Retries

Creating money-movement requests — `Generate Transaction` (Payments API) and `Process Disbursement` (Disbursement API) — is the one place where a careless retry can cost real money. This page explains how `reference_no` protects you and how to retry safely when a request fails or times out.

## reference\_no Is Your Idempotency Key

Both write endpoints require a `reference_no` that **you** supply:

* `POST /v1/biller/transactions/generate` (Generate Transaction)
* `POST /v1/api/disbursement/process` (Process Disbursement)

A `reference_no` can be used **once**. After it has been used, the server will not process that same `reference_no` again — a repeat submission does **not** create a second transaction or a second payout. Creating a genuinely new, separate transaction requires a **new** `reference_no`.

Because you choose the value and it can only be used once, `reference_no` acts as a natural idempotency key. You do **not** need a separate idempotency header — the business key you already send is the guarantee.

This is what protects you from double-charging a customer or paying a recipient twice. It only works if you keep the **same** `reference_no` across retries of the **same** intent. Generating a fresh `reference_no` for a retry defeats the protection and can result in a duplicate payout.

## Generate reference\_no Once, Persist It Before the Call

Treat `reference_no` as belonging to the *intent to pay*, not to an individual HTTP request.

#### Generate it once per intent

Create a single, unique `reference_no` when the payment or payout is first decided — one value per customer checkout or per payout instruction.

#### Persist it before the first API call

Save the `reference_no` to your own database **before** you send the request. If your process crashes mid-request, you still know which `reference_no` was in flight and can look it up rather than starting over with a new one.

#### Reuse the same value on every retry

Any retry of the same intent must send the identical `reference_no`. Never allocate a new one just to "try again."

## The Safe-Retry Rule

When a create call fails ambiguously — a timeout, a dropped connection, or a `5xx` where you never saw a response — you don't know whether the server processed it. Do **not** guess.

On an ambiguous failure, do **not** resubmit with a **new** `reference_no`, and do **not** assume the first attempt failed. Either mistake can create a second, real payout.

Instead, resolve the uncertainty by **looking up the original `reference_no`**:

#### Look up the reference\_no

Query the transaction by its `reference_no` (see [Looking Up Transactions](/looking-up-transactions)) to find out whether the first attempt actually went through.

#### If it exists

The request succeeded. Use the returned status — do not send it again. Resubmitting the same `reference_no` will be rejected anyway.

#### If it does not exist

The request never landed. It is safe to resubmit **with the same `reference_no`**.

Prefer webhooks for the outcome itself. Configure `callback_webhook_url` (see [Webhooks & Redirects](/webhooks-and-redirects)) so you're notified when the transaction settles, and treat the `reference_no` lookup as the tool for resolving *"did my create call land?"* after an ambiguous failure.

## Summary

| Situation                                   | Do this                                                                             |
| ------------------------------------------- | ----------------------------------------------------------------------------------- |
| First attempt at a new payment/payout       | Generate a new `reference_no`, persist it, then send.                               |
| Retrying after a timeout or ambiguous error | Reuse the **same** `reference_no`; look it up first to see if it already succeeded. |
| Genuinely new, separate transaction         | Use a **new** `reference_no`.                                                       |
| Confirmed the original succeeded            | Don't resend — the `reference_no` is already spent.                                 |