Idempotency

Understand safe retry behavior for commands
View as Markdown

When a command returns 202 Accepted, it enters an async processing pipeline. If your request times out or you lose the response, you need to know whether it’s safe to retry.

Safe-by-design commands

Many Clossir API commands are idempotent by design — repeating the same request produces the same outcome without side effects:

OperationIdempotent byHow it works
Create attestationpartyId + type + chainEidDuplicate request returns the existing attestation
Deploy assetassetId + chainEidRe-deploy to the same chain is a no-op if already deployed
Create organizationexternalIdSame external ID returns the existing org

For these endpoints, retrying after a timeout is safe — the API detects the duplicate and returns the existing resource.

Commands with side effects

Some commands are not inherently idempotent:

OperationWhy
Create transferEach call creates a new transfer
Create distributionEach call queues a new distribution

For these, use the requestId from the 202 response to check whether the original command was received before retrying:

safe-retry.ts
1import { SignumProblemDetailsError } from "@signum-tech/sdk/models/errors";
2
3async function createTransferSafely(payload: TransferPayload) {
4 try {
5 return await signum.transfers.postTransfers(payload);
6 } catch (e) {
7 if (e instanceof SignumProblemDetailsError && e.status === 409) {
8 // 409 Conflict — transfer already exists
9 // The original request was processed; fetch it instead
10 console.log("Transfer already created");
11 return;
12 }
13 throw e;
14 }
15}

Best practices

  1. Store the response — always persist the 202 response body (especially requestId and the resource ID) before moving on. If your process crashes after receiving the response, you can look up the resource.

  2. Use webhooks — for critical commands, subscribe to webhooks rather than relying on polling. The webhook payload includes the requestId from your original command. See Async Operations for the correlation pattern.

  3. Configure retries conservatively for commands — the SDK’s built-in retry handles transport errors (timeouts, 5xx). For 202 commands, the retry is safe because the server didn’t process the command if it returned a 5xx or timed out:

command-retries.ts
1const result = await signum.transfers.postTransfers(payload, {
2 retries: {
3 strategy: "backoff",
4 backoff: {
5 initialInterval: 1000,
6 maxElapsedTime: 30_000,
7 },
8 },
9});

Next steps