Idempotency¶
When you submit a POST request that creates a Transaction, Batch, Subclient, Subclient Account, or other resource, your network may drop the response, your client may time out, or your job runner may retry. Without a guard against duplicates, a retry can create a duplicate, and in a payments API, duplicates move real money you didn't intend to move.
GIB exposes a body-level field, external_key, that you populate with a unique value per logical operation. The API treats external_key as the duplicate-detection key. Submitting the same external_key twice (with the same User and Type) returns 409 ConflictError instead of creating a second resource.
How it works¶
Include external_key in the request body on any POST that creates a resource:
POST /transaction/from-subledger
{
"external_key": "TX-2026-05-04-payroll-employee-42",
"type": "ach",
...
}
The value is your choice; UUIDv4 works, but a deterministic string derived from your domain (e.g. payroll-{run_id}-{employee_id}) is often clearer for operational debugging.
First call¶
GIB processes the request normally and stores the resource with the external_key recorded against the (User, Type) tuple.
Retry, same external_key¶
A second request with the same external_key (same User, same Type) returns 409 ConflictError. The response includes the X-Duplicate-Transaction-Transfer-Keys header listing the original Transaction(s) so your code can correlate.
HTTP/1.1 409
X-Duplicate-Transaction-Transfer-Keys: 0987654321
Content-Type: application/json
{
"error": "ConflictError: Duplicate transaction found",
"exception_class": "ConflictError"
}
Unlike a header-based idempotency system, GIB does not replay the original
200response. A duplicateexternal_keyalways produces a409. Treat the409as confirmation that your earlier request landed.
Worked example: recovering from a dropped response¶
The pieces above combine into one safe retry sequence. Suppose you submit a transaction and never see the response:
- Submit the
POSTwith anexternal_key. The network drops the response, so your client times out without knowing whether the transaction was created. - Retry the identical request, same
external_key, same User and Type. - Read the outcome. A
409 ConflictErrormeans your first request did land, treat it as success, not failure. (A2xxinstead means the first request never landed and this retry created the transaction.) - Correlate. On the
409, read theX-Duplicate-Transaction-Transfer-Keysresponse header for the original transaction's transfer key(s). - Confirm (optional). Retrieve the original's current state with
getTransactionsif you need its fields or status.
Either branch leaves you with exactly one transaction and its transfer key, and no duplicate movement of funds.
Retry without external_key¶
If you didn't provide an external_key, GIB still attempts duplicate detection using a fallback fingerprint of core fields for that resource type. For Transactions this includes fields such as User, Type, Transfer Type, Amount, and routing/account numbers; for other resource types (Subclients, etc.) it matches on fields like name and type.
This fallback covers the case where you forget the external_key, but it's brittle: a legitimate second operation with the same core fields within a short window can also trip the check. Always send external_key for production traffic.
The exact fields used differ per operation and are documented in each operation's description in the API reference.
Overriding a duplicate (transactions only)¶
If you have a legitimate need to force-submit a transaction that GIB flagged as a duplicate (for example, a genuine re-run of a payment that was already returned), include the X-Duplicate-Override-Reason request header with a plain-text explanation:
POST /transaction/from-subledger
Authorization: <key>
X-Duplicate-Override-Reason: Legitimate re-submission after R01 return on 2026-05-04
GIB will process the request and log the reason against the transaction for audit purposes. The header is optional; omitting it means any detected duplicate still returns 409.
Key generation¶
A good external_key is:
- Unique per logical operation. Don't reuse a key for two different transactions, even if they look similar.
- Stable across retries. A retry of the same operation must use the same key. That's the whole point.
- Generated client-side. GIB never generates the key; you do.
- Deterministic when possible. If your handler is processing an event from another system, derive the key from that event's ID. Every retry of your handler computes the same key.
UUIDv4 is the simplest:
For event-driven workflows:
When to use it¶
Always, on any POST that creates a resource. Specifically:
- Creating ACH, Wire, ICL, or BOOK Transactions (every rail)
- Submitting a Batch or file
- Creating Subclients and Subclient Accounts
- Submitting reconciliation data
It costs nothing extra and protects you from a class of bugs that's expensive to debug after the fact.
Idempotency vs. duplicate detection¶
These are the same mechanism in GIB's API: external_key drives both. Where the field is empty, the API falls back to fingerprint-based duplicate detection on core fields for that resource type. See Security → Duplicate Detection.
Related concepts¶
- Errors, the
409 ConflictErrorreturned on duplicate submission - Security → Duplicate Detection, the fingerprint fallback
- Webhooks, at-least-once delivery, make your handlers idempotent
- Companies & Users,
external_keyis scoped per User