Idempotency and async settlement
Learn where Idempotency-Key deduplicates queued transactions and use Prefer to wait for on-chain settlement or return immediately.
Overview
An on-chain write can continue after its HTTP request ends. The transaction queue uses Idempotency-Key to deduplicate work that reaches it, but a route may do other work first. Check the route's replay contract and original request status before retrying an uncertain instruction. Prefer controls how long the API waits before returning a status URL.
Transaction-writing requests are asynchronous by default. DALP queues the instruction and returns 202 Accepted with a status URL. A timeout does not cancel the queued work.
Idempotency-Key
Idempotency-Key identifies one client-side instruction. The queue can attach a matching submission to the same transaction request, but this does not prove that the entire API route is replay-safe. A route promises the same outcome on replay only when its contract says so and its response echoes the effective key. The table below describes queue matching after a request reaches that boundary.
Use a new key for each distinct instruction. On a route that documents replay safety, retain that key across retries. A duplicate mint or transfer moves value twice.
DALP accepts the header under the exact spelling Idempotency-Key, idempotency-key, or IDEMPOTENCY-KEY, and trims a quoted value. An empty value counts as no key at all, whether you send it bare or as a pair of quotes. If you omit the header, DALP generates one for that request, but you have nothing to retry with if the response is lost. Send your own key on a route that documents replay safety.
The transaction queue keys retry identity to your Idempotency-Key and to the write it names for 24 hours after the first request: the wallet selection (accountWalletId, smartWalletAddress, forceEoa) and the call itself (operation, target contract, call data, and value). It rejects a different write under the same key with 409 Conflict (exec.queue.IDEMPOTENCY_CONFLICT). This queue rule does not cover route work before admission.
| Situation | DALP behavior |
|---|---|
| Same key, same write, within 24 hours | Attaches to the original instruction and returns its current transaction id, state, and result fields, whether or not the original request has finished processing. Never submits a second transaction for a write that could have reached the chain. |
| Same key, different call (operation, target contract, call data, or value), within 24 hours | Rejected with 409 Conflict, not retryable. Send a new key for the changed instruction. |
Same key, different wallet selection (accountWalletId, smartWalletAddress, or forceEoa), within 24 hours | Rejected with 409 Conflict, not retryable. Send a new key for the changed wallet selection. |
| Same key, reused after the 24-hour window | Rejected with 409 Conflict, not retryable. Confirm the earlier instruction's outcome, then retry with a new key. |
After a timeout, inspect the original operation's status before sending another write; the first may still complete. Retry with the original Idempotency-Key only when the route documents replay safety. Past the queue's 24-hour window, the same key no longer attaches to the earlier queue request, so confirm its outcome before starting over with a new key.
Concurrent requests and changed bodies
Concurrent transaction requests with the same sending wallet, chain, and idempotency key attach to one instruction. A retry can return an in-flight state; it need not wait for the original request to finish before being recognized.
A reused key is compared against the write it first named, not only the wallet selection. Reusing a key with a different recipient or amount changes the call data, so DALP rejects it with 409 Conflict instead of returning the earlier instruction. Assign a distinct key to every distinct instruction.
At the queue boundary, one reuse is allowed on purpose: when the earlier write failed before anything was signed, a matching submission under the same key enqueues a fresh attempt. The failed attempt keeps its own transaction id and no longer answers that key. This does not make the whole API route safe to repeat.
A retry retains the original transaction-request identity. Reconcile by that identity and the resulting chain transaction. Request logs, workflow attempts, and settlement records describe different stages; do not assume they all have one row per instruction.
Prefer: choose sync or async
Transaction routes default to asynchronous: DALP queues the operation and returns 202 Accepted immediately. Use Prefer to change that per request.
| You send | Mode | What DALP does |
|---|---|---|
| Nothing | Async (the default) | Returns 202 Accepted with a status URL immediately. Poll it until the operation reaches a terminal state. |
Prefer: respond-async | Async | Same as the default, sent explicitly. |
Prefer: wait=N | Sync | Holds the response for up to N seconds, clamped between 5 and 99. Returns 200 OK with the settled result if the operation finishes in time; otherwise degrades to 202 Accepted. |
Prefer: respond-async, wait=N | Hybrid | Same clamped wait, requested alongside the explicit async preference. |
A synchronous wait never changes the outcome of the operation, only whether DALP returns the settled result inline or hands you a status URL. DALP echoes the directives it actually honored in the Preference-Applied response header, per RFC 7240.
For example, Prefer: wait=30 waits up to 30 seconds for settlement. If the wait expires first, DALP returns the queued handle. See the V2 mint endpoint for the complete mint request and response schema.
Send Idempotency-Key alongside Prefer on a replay-safe synchronous route. If a response is lost, inspect the original operation status before retrying.
A settled response names the transaction request behind the write in meta.transactionId. On a replay-safe route whose 202 Accepted handle names that same request as transactionId, a retry that returns 200 OK after the work finished answers with the id you already hold. Send that value to the transaction-request routes to read status or to retry a failed request.
The field is present when the settled body was built from a queue result that still holds the request, including one that completed without submitting a chain transaction, where meta.txHashes is empty and meta.transactionId still names the request. It is absent otherwise, so treat a missing key as no id to correlate with rather than as proof that no request exists. Read the key rather than assuming a position: one transaction request can submit several chain transactions, so the hash count says nothing about the request count.
Terminal states and retrying a failed transaction
Whether you poll the status URL or wait synchronously, the current wait ends when a transaction request reaches COMPLETED, FAILED, DEAD_LETTER, or CANCELLED. Only COMPLETED and CANCELLED are immutable. A FAILED request can move to DEAD_LETTER, and an operator can force-retry a FAILED or DEAD_LETTER request to move it back to QUEUED. If the requeued request remains unclaimed because dispatch failed, repeating force-retry resumes that attempt; if its run already ended, it starts a new attempt.
FAILED and DEAD_LETTER both carry errorMessage, a plain-text description such as "Execution reverted". When the failure maps to a known catalog entry, the response also carries contractError, with the same id, why, fix shape the error envelope uses elsewhere; read errorMessage when contractError is absent.
A failed transaction does not retry itself. The queue matches the same key to its existing transaction request; replaying the API route may run other work before that match. An operator with the organization update permission can requeue a FAILED or DEAD_LETTER request through the transaction-request retry endpoint. After this recovery, poll for the active state or result. If you submit a separate write instead, first read errorMessage or contractError.fix to decide whether retrying can succeed, then use a new Idempotency-Key.
Next steps
Versioning and deprecation
Breaking API changes ship only on a new /api version, and a deprecated operation answers with Deprecation and Sunset headers for at least 90 days.
Onboard an investor
Create a user through the API, resolve the participant's identity wallet, register the identity under a country, and verify the investor with Sumsub.