Handle Errors and Retries
Implement retry logic, idempotency, and error recovery for your bridge
Bridge integration involves distributed coordination between the ledger and your external system. Network failures, temporary outages, and processing errors are inevitable. This page covers how the ledger handles retries, how your bridge should implement idempotency, and the critical error boundaries of the two-phase commit protocol.
Retry mechanism
When the ledger sends a request to your bridge and does not receive a successful response (or receives no response at all), it retries with exponential backoff. This applies to all request types: prepare, commit, abort, status notifications, and effect webhook deliveries.
Default retry settings:
- Initial delay: 1 second between the first request and the first retry.
- Backoff coefficient: each subsequent retry occurs after a delay 20% longer than the previous one.
- Maximum delay: the delay caps at 1 hour. Once reached, retries continue hourly.
- Maximum retries: 5 attempts before the ledger gives up on delivery.
These defaults are configurable server-wide and individually per request type. In extreme cases the ledger may apply additional criteria for terminating the retry sequence, such as total timeout, as defined in operational policies.
When your bridge comes back online after a period of downtime, call POST /v2/bridges/:handle/activate to reschedule all pending deliveries with no delay. This also re-queues deliveries the ledger abandoned after exceeding the maximum retry count. Without activation, your bridge might wait up to 1 hour for the next retry cycle to begin.
Idempotency
The retry mechanism means your bridge may receive the same request more than once. A debit prepare that timed out on the network could arrive twice. A commit that the ledger retried before your first 202 Accepted response reached it could produce a duplicate. Your bridge must handle repeated calls gracefully.
The idempotency key is the entry handle — deb_* for debits, cre_* for credits. Each handle is unique within the intent's lifecycle and stable across retries.
Implementation pattern:
- When your bridge receives a prepare, commit, or abort call, check whether you have already processed that handle for that phase.
- If already processed (same handle, same phase): respond
202 Acceptedwithout re-processing. The proof was already submitted. - If not yet processed: save the entry state (handle, phase, timestamp) before starting work, then process normally.
- Track the progression: a handle moves through prepare → commit or prepare → abort. Never skip phases.
This state tracking also protects against split-brain scenarios. If your bridge crashes mid-processing and the ledger retries, you can detect whether the previous attempt completed (proof submitted) or needs to be re-executed.
The commit-never-fails contract
This is the most critical rule of bridge integration. The two-phase commit protocol has a sharp dividing line between phases where errors are expected and phases where they are not.
Commit and abort must always succeed. Once the ledger sends a commit or abort request, your bridge must complete the operation. Never return a failure. The ledger will not accept a failed proof from commit or abort.
Before finalization — prepare phase
Errors during prepare are expected and handled. This is where your bridge performs all validation: checking balances, verifying account status, enforcing limits, running compliance rules. If any check fails, your bridge submits a proof with status failed along with a reason code and detail message. The ledger then aborts the entire intent — all participants receive an abort call.
This is normal operation. The prepare phase exists specifically to catch problems before real money moves.
After finalization — commit and abort phases
Once the ledger sends commit, it means every participant has already accepted the prepare. The distributed system has collectively agreed the operation can proceed. A failure at this point would leave the system in an inconsistent state: one side committed, the other did not. The 2PC protocol guarantees atomicity only if commit and abort always succeed.
If the banking core is temporarily unavailable during commit, your bridge must retry internally — with its own backoff strategy, queuing, or circuit breaker. If the core returns an unexpected error, your bridge must investigate and resolve it before submitting the committed proof. The ledger will wait.
The same applies to abort. If funds were deducted during prepare, the bridge must create a reversal transaction. If a hold was placed, it must be released. The abort operation must fully unwind everything the prepare phase set up.
Summary
| Phase | Can fail? | What happens on failure |
|---|---|---|
| Prepare | Yes | Submit proof with status failed → ledger aborts entire intent |
| Commit | No | Must retry internally → always submit committed proof |
| Abort | No | Must reverse all side-effects → always submit aborted proof |
Error proof format
When your bridge rejects an operation during the prepare phase, it submits a proof with status failed to POST /v2/intents/:handle/proofs:
{
"method": "ed25519-v2",
"public": "<bridge-signer-public-key>",
"digest": "<hash>",
"result": "<signature>",
"custom": {
"moment": "2025-05-14T14:23:45.221-05:00",
"handle": "deb_01u9RGCevt4rEkRMV",
"status": "failed",
"reason": "bridge.account-insufficient-balance",
"detail": "Insufficient balance in account: 85620797"
}
}The reason field should use a structured error code. The detail field provides a human-readable explanation useful for debugging and support.
Common reason codes:
bridge.account-insufficient-balance— source account does not have enough fundsbridge.account-not-found— the source or target account does not exist in the core systembridge.account-frozen— the account is suspended or blockedbridge.account-inactive— the account exists but is not in an active statebridge.validation-failed— a general validation failure (compliance, limits, blacklists)bridge.authorization-failed— the operation is not authorized for this account
For the full proof structure and submission endpoint, see Get Notifications — Proof Submission.