Async Operations

Handle the CQRS command pattern with 202 responses and webhook correlation
View as Markdown

The Clossir API follows a CQRS (Command Query Responsibility Segregation) pattern. Understanding this split is essential for integrating correctly.

Reads vs commands

Operation typeHTTP methodStatus codeBehavior
Read (query)GET200 OKReturns current state immediately
Write (command)POST, PUT, DELETE202 AcceptedQueues the operation for async processing

Reads hit the database directly and return the current projection. Commands are queued and processed asynchronously by dedicated workers — they don’t execute inline with your request.

The 202 response

When you issue a command, the API returns 202 Accepted with a response body containing the resource ID and a pending status:

create-transfer.ts
1const result = await signum.transfers.postTransfers({
2 fromWallet: "0xabc...sender",
3 toWallet: "0xdef...receiver",
4 assetId: "ast_abc123",
5 amount: "1000",
6});
7
8console.log(result.accepted); // true
9console.log(result.transferId); // "txn_xyz789"
10console.log(result.status); // "pending"
11console.log(result.message); // human-readable confirmation

At this point the transfer is queued, not complete. The system will evaluate compliance policies, execute the on-chain transaction, and update the status asynchronously.

Some endpoints also return a requestId field for webhook correlation (see below).

Checking status (polling)

Poll the corresponding GET endpoint with the resource ID to check progress:

poll-status.ts
1async function waitForTransfer(transferId: string): Promise<void> {
2 const maxAttempts = 30;
3 const intervalMs = 2000;
4
5 for (let i = 0; i < maxAttempts; i++) {
6 const transfer = await signum.transfers.getTransfersByTransferId({
7 transferId,
8 });
9
10 if (transfer.status === "completed") {
11 console.log("Transfer settled:", transfer);
12 return;
13 }
14
15 if (transfer.status === "failed") {
16 throw new Error(`Transfer failed: ${transfer.failureReason}`);
17 }
18
19 // Still pending — wait and retry
20 await new Promise((r) => setTimeout(r, intervalMs));
21 }
22
23 throw new Error("Transfer did not settle within timeout");
24}

Webhook correlation (preferred)

Instead of polling, subscribe to webhooks for real-time notifications. Endpoints that return a requestId in their 202 response (such as asset deployment and bridge operations) allow you to correlate the webhook payload with the original command:

POST /orgs/:id/assets/:id/deploy → 202 { requestId: "req_abc", assetId: "ast_xyz", status: "pending" }
... async processing ...
Webhook → { event: "asset.deployed", requestId: "req_abc", assetId: "ast_xyz", ... }

For endpoints that return a resource ID instead (like transferId), match on that ID in the webhook payload.

Webhooks are more efficient than polling and give you immediate notification when processing completes. See the API Reference for the webhook payloads and event types.

Common 202 endpoints

EndpointID in responseWhat happens async
POST /attestationsattestationIdOn-chain attestation settlement
POST /transferstransferIdCompliance check + on-chain transfer
POST /orgs/:id/assetsassetIdAsset registration
POST /orgs/:id/assets/:id/deployrequestId, assetIdOn-chain contract deployment
POST /orgs/:id/assets/:id/bridgerequestIdCross-chain bridge execution

React integration

With @signum-tech/sdk-react, use useSignumMutation for commands and useSignumQuery with refetchInterval for polling:

use-transfer.tsx
1import { useSignumMutation, useSignumQuery } from "@signum-tech/sdk-react";
2
3function TransferButton({ payload }: { payload: TransferPayload }) {
4 const [transferId, setTransferId] = useState<string | null>(null);
5
6 const mutation = useSignumMutation({
7 mutationFn: (client, variables: TransferPayload) =>
8 client.transfers.postTransfers(variables),
9 onSuccess: (data) => setTransferId(data.transferId),
10 });
11
12 const status = useSignumQuery({
13 queryKey: ["transfer", transferId],
14 queryFn: (client) =>
15 client.transfers.getTransfersByTransferId({ transferId: transferId! }),
16 enabled: !!transferId,
17 refetchInterval: (query) =>
18 query.state.data?.status === "pending" ? 2000 : false,
19 });
20
21 // render based on mutation + status states...
22}

See React Integration for the full sdk-react guide.

Next steps