> ## Documentation Index
> Fetch the complete documentation index at: https://docs.niobi.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> How Niobi pairs merchant references with unique system IDs across collections and payouts, and enforces duplicate-transaction prevention on payouts.

## How Niobi Ensures Idempotency

In financial API integrations, network drops, client timeouts, and automated retries can cause the same API request to be transmitted multiple times.

**Idempotency** guarantees that initiating the same payment operation multiple times produces the exact same outcome as initiating it once. It prevents double-payouts to recipients, duplicate prompt pushes to payers, and replay attacks.

***

## The Dual-Reference Architecture

Every transaction that reaches Niobi operates on two distinct, complementary identifiers:

1. **Niobi Transaction Reference (`depositId` / `reference`):**
   * Generated automatically by Niobi when an API request is initialized (`depositId` for collections, `reference` for payouts).
   * Returned in the initial API response upon request initiation.
   * Included in all subsequent asynchronous callbacks (`payment_step: 2`) and status queries.

2. **Merchant Reference (`third_party_reference_1` / `payment_reference`):**
   * Generated on your backend before dispatching the API request (e.g. `ORDER-2026-001` or `TX-DISB-9901`).
   * Represents your internal transaction or order identifier.
   * Serves as your idempotency key to prevent duplicates and enable automated reconciliation.

<div className="pstep-flow">
  <div className="pstep-card pstep-card--initial">
    <div className="pstep-card-header">
      <span className="pstep-badge pstep-badge--initial">Your System</span>
      <span className="pstep-label">Merchant Tracking Layer</span>
    </div>

    <code className="pstep-code">third\_party\_reference\_1: "TX-ORD-9001"</code>
    <code className="pstep-code">payment\_reference: "PAYOUT-REF-1001"</code>
  </div>

  <span className="pstep-arrow">→</span>

  <div className="pstep-card pstep-card--terminal">
    <div className="pstep-card-header">
      <span className="pstep-badge pstep-badge--terminal">Niobi System</span>
      <span className="pstep-label">Niobi Transaction Layer</span>
    </div>

    <code className="pstep-code">depositId: "dep\_12345678"</code>
    <code className="pstep-code">reference: "ref\_98765432"</code>
    <p className="pstep-desc">Returned upon initial request.</p>
  </div>
</div>

***

## How It Works

Niobi binds your merchant reference to a unique transaction reference upon initial receipt. When a reference uniqueness flag (`is_third_party_reference_1_unique: 1` and/or `is_third_party_reference_2_unique: 1`) is enabled, Niobi enforces duplicate prevention. This active duplicate rejection is currently enforced on disbursements (payouts) only: collection requests accept `third_party_reference_1`/`third_party_reference_2` and their uniqueness flags and Niobi still stores them against the transaction, but a duplicate collection request is not currently rejected based on these flags.

```mermaid theme={null}
%%{init: {'themeVariables': {'fontSize': '23px', 'actorBorder': '#0D9373', 'actorLineColor': '#0D9373', 'signalColor': '#0D9373', 'noteBorderColor': '#0D9373'}, 'sequence': {'width': 220, 'height': 95, 'actorMargin': 80, 'messageMargin': 60, 'boxMargin': 18, 'noteMargin': 18}}}%%
sequenceDiagram
    autonumber
    participant Merchant as Your Application
    participant Niobi as Niobi API Gateway

    Merchant->>Niobi: 1. POST Request (third_party_reference_1: "TX-ORD-9001", is_third_party_reference_1_unique: 1)
    Note over Niobi: 2. Generates Unique Reference<br/>Records "TX-ORD-9001"<br/>Processes transaction
    Niobi--xMerchant: Connection Dropped / HTTP 504 Timeout
    Note over Merchant: Network timeout! Outcome unknown.
    
    rect rgb(240, 253, 244)
    Note over Merchant: SAFE RETRY: Re-send with SAME third_party_reference_1 and uniqueness flag
    Merchant->>Niobi: 3. POST Retry (third_party_reference_1: "TX-ORD-9001", is_third_party_reference_1_unique: 1)
    Note over Niobi: 4. Uniqueness Check (Flag = 1):<br/>Matches "TX-ORD-9001" to existing record<br/>Rejects duplicate request (Does NOT re-debit or create duplicate)
    Niobi-->>Merchant: Duplicate rejected: "Duplicate third_party_reference_1 was found!"
    end
```

### 1. Request Initiation & Transaction Reference Assignment

When your server submits a payout or collection request, Niobi assigns a unique transaction reference and maps your `third_party_reference_1` to the record.

### 2. Active Duplicate Prevention (`is_third_party_reference_1_unique` / `is_third_party_reference_2_unique`)

To prevent accidental double-payouts during retries or network timeouts, include `"is_third_party_reference_1_unique": 1` and/or `"is_third_party_reference_2_unique": 1` in `params`:

```json theme={null}
{
  "is_third_party_reference_1_unique": 1,
  "is_third_party_reference_2_unique": 1
}
```

<Note>
  **Flag Requirement:**\
  Duplicate detection **only executes for a reference whose flag is passed as `1`** in the request payload. If a flag is omitted or set to `0`, Niobi will not check that reference and will process the retry as a new transaction. If both flags are set to `1`, Niobi checks `third_party_reference_1` first, then `third_party_reference_2`, and immediately rejects the request on whichever one first matches an existing record, without evaluating further.
</Note>

When enabled, Niobi verifies uniqueness before debiting or dispatching funds. If a transaction with that reference already exists in the system, Niobi immediately rejects the duplicate submission, naming whichever reference actually matched:

```json theme={null}
{
  "success": false,
  "message": "We apologize as we were not able to process your payment request. Please try again later.",
  "data": {
    "error": true,
    "message": "Duplicate third_party_reference_1 was found!"
  }
}
```

***

## Terminal States & Reference Binding

Once a transaction reaches a **terminal status** ([`000000 - Success`](/errors/000000-success) or [`000005 - Failed`](/errors/000005-failed)), both your merchant reference and Niobi's transaction reference are permanently recorded:

* **Finality:** A terminal status indicates the payment has completed.
* **Duplicate Protection:** Resubmitting with the same `third_party_reference_1` when `is_third_party_reference_1_unique: 1` is passed immediately rejects duplicate execution (`Duplicate third_party_reference_1 was found!`).
* **Verifying Outcome:** To check the state of an already-submitted transaction, query the [Get Transaction Status API](/transaction-status) rather than re-initiating the request.
* **Failed Transactions:** A `000005 - Failed` status represents finality (such as invalid account details, payer decline, or timeout).

***

## Retrying Failed Transactions

If a transfer or collection fails definitively and you need to re-attempt the payment (such as after the customer provides a new phone number or corrects their account details), **you must use a new `third_party_reference_1`**.

The previous reference cannot be reused to initiate a new transfer.

### Recommended Pattern: Append a Retry Suffix

```json theme={null}
// Original attempt
"third_party_reference_1": "payout-2026-001"

// First retry attempt (e.g. after correcting phone number)
"third_party_reference_1": "payout-2026-001-retry-1"

// Second retry attempt (if needed)
"third_party_reference_1": "payout-2026-001-retry-2"
```

### Benefits of the Suffix Pattern:

* **Traceability:** Correlate all attempts related to the same underlying logical order.
* **Webhook Mapping:** Match asynchronous callback notifications (`payment_step: 2`) back to the specific retry attempt.
* **Audit Trail:** Maintain an immutable history in your database of each payment attempt and its specific outcome.

***

## Uniqueness Identifiers Reference

| Identifier                    |  Defined By  |       Location      | Purpose & Role                                                                                                                            |
| :---------------------------- | :----------: | :-----------------: | :---------------------------------------------------------------------------------------------------------------------------------------- |
| **`third_party_reference_1`** | **Merchant** |   Request `params`  | **Primary Merchant Idempotency Key.** Your unique internal order ID. Used to prevent duplicates on retries.                               |
| **`payment_reference`**       | **Merchant** |   Request `params`  | Merchant tracking reference specific to disbursement instructions.                                                                        |
| **`third_party_reference_2`** | **Merchant** |   Request `params`  | Optional secondary reference (e.g. batch ID, customer ID, or invoice number).                                                             |
| **`depositId`**               |   **Niobi**  | Response / Callback | Unique deposit reference returned by Niobi when initiating a collection request and in collection callbacks.                              |
| **`reference`**               |   **Niobi**  | Response / Callback | Unique transaction identifier returned by Niobi when initiating a request. Included in subsequent status queries and webhook callbacks.   |
| **`salt`** & **`timestamp`**  | **Merchant** |    Root Envelope    | Cryptographic nonce and Unix timestamp included in every request envelope to guarantee unique HMAC signatures and prevent replay attacks. |

***

## Webhook Callback Idempotency on Your Server

Because webhook callbacks traverse public networks, Niobi may re-deliver a terminal callback (`payment_step: 2`) if your server took longer than 5 seconds to respond with HTTP `200 OK` or encountered temporary connectivity issues.

Ensure your webhook listener handles duplicate callbacks safely:

```mermaid theme={null}
flowchart TD
    A["Incoming Callback (payment_step: 2)"] --> B["1. Verify HMAC Signature"]
    B --> C["2. Extract third_party_reference_1 and reference"]
    C --> D{"3. Check Local Database:<br/>Is transaction already marked SETTLED?"}
    D -- "Already Processed" --> E["Safely Return HTTP 200 OK (Do not duplicate balance credits)"]
    D -- "Not Yet Processed" --> F["4. Acquire Row Lock (SELECT ... FOR UPDATE)"]
    F --> G["5. Credit User Account / Fulfill Order"]
    G --> H["6. Set status = 'SETTLED' in DB"]
    H --> I["Return HTTP 200 OK"]
```

### Callback Receiver Best Practices:

1. **Verify Inbound Signature:** Always verify the incoming SHA-256 signature using your integration Secret Key (`senderKey`) before parsing data.
2. **Check State Before Crediting:** Check whether `third_party_reference_1` or Niobi's `reference` has already been credited in your database.
3. **Return HTTP 200 on Duplicates:** If a callback is received for an already settled order, acknowledge receipt immediately with HTTP `200 OK` without duplicating financial credits.
4. **Use Concurrency Locks:** Use database row-level locking or unique constraints on `third_party_reference_1` to prevent concurrent threads from processing duplicate webhooks simultaneously.

***

## Next Steps

Explore related operational and security guides:

<div className="next-steps-flow">
  <a href="/transaction-status" className="next-step-card">
    <div className="next-step-badge">Reconciliation</div>
    <h3>Transaction Status & Polling</h3>
    <p>Authoritative guide on querying transaction status and executing dual-layer reconciliation.</p>
  </a>

  <div className="next-step-arrow">
    <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path d="M5 12h14M12 5l7 7-7 7" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  </div>

  <a href="/errors/overview" className="next-step-card">
    <div className="next-step-badge">Error Codes</div>
    <h3>Error Handling & Status Codes</h3>
    <p>Understand status codes, failure diagnostics, and appropriate recovery workflows.</p>
  </a>
</div>
