Skip to main content

Overview

Every transfer in QC Custody follows a deterministic state machine. Understanding these states is critical for building robust integrations — your system should handle each transition and respond to webhook events accordingly.

State machine diagram

States explained

Active states

Transaction has been created and validated. The service has estimated gas and assigned a nonce. Policy evaluation happens immediately — the transaction automatically transitions to SIGNING_REQUESTED, PENDING_APPROVAL, or REJECTED.
A policy rule (e.g., REQUIRE_APPROVAL) has flagged this transaction. It remains here until an authorized user calls POST /transactions/{id}/approve or POST /transactions/{id}/reject.Webhook event: approval.required
An approver has approved the transaction. It immediately transitions to SIGNING_REQUESTED.Webhook event: approval.decision
The transaction is ready for external signing. Call GET /transactions/{id}/signing-payload to retrieve the Keccak256 hash, sign it with your quantum-safe private key, and submit via POST /transactions/{id}/sign.Webhook event: transaction.status_changedThis is the key integration point — see the External signing guide.
The signature has been validated. The service is assembling the raw signed transaction.
The signed transaction is being sent to the Quantum Chain network via RPC.
The transaction has been broadcast and is awaiting on-chain confirmation. The service polls for the transaction receipt and tracks the confirmation depth.

Terminal states

The transaction has reached the required confirmation depth. The chainTxHash, blockNumber, and confirmedAt fields are populated.Webhook event: transaction.completed
A policy denied the transaction (e.g., amount exceeds limit, destination blacklisted) or an approver explicitly rejected it. Check failureReason for details.
The transaction failed during broadcast (node rejected it) or during confirmation (reverted on-chain, dropped from mempool). Check failureReason.Webhook event: transaction.failed
The transaction was cancelled by the integrator or by timeout before reaching a final on-chain state.
The submitted signature was invalid — wrong key, corrupted signature, or public key mismatch.

Happy path timeline

Here’s what a typical successful transfer looks like:
StepStateWhoDuration
1CREATEDAPI callerInstant
2SIGNING_REQUESTEDAuto (policy engine)< 100ms
3SIGNEDExternal signerDepends on your infra
4BROADCASTINGCustody service< 1s
5CONFIRMINGCustody serviceNetwork block time
6COMPLETEDCustody serviceConfirmation depth reached

Polling vs. webhooks

You can track transaction status two ways:

Polling

Call GET /transactions/{id} periodically. Simple but adds latency and load.
curl https://api.quantumchain.io/v1/transactions/{id} \
  -H "Authorization: Bearer $API_KEY"

Webhooks (recommended)

Register a webhook endpoint and receive real-time events at every state transition.See the Webhooks guide.

Filtering transactions

List transactions with filters for monitoring:
# All confirming transactions
curl "https://api.quantumchain.io/v1/transactions?status=CONFIRMING" \
  -H "Authorization: Bearer $API_KEY"

# All transactions for a specific vault
curl "https://api.quantumchain.io/v1/transactions?vaultId=vault-uuid" \
  -H "Authorization: Bearer $API_KEY"

# All transactions for a specific wallet and asset
curl "https://api.quantumchain.io/v1/transactions?walletId=wallet-uuid&assetId=asset-uuid" \
  -H "Authorization: Bearer $API_KEY"

Error handling

Always check failureReason on terminal states (FAILED, REJECTED, SIGN_FAILED, CANCELLED). It contains a human-readable explanation of what went wrong.
Common failure reasons:
ReasonStateResolution
amount exceeds maxREJECTEDLower the amount or adjust the MAX_AMOUNT policy
destination is blacklistedREJECTEDRemove the address from the blacklist policy
nonce too lowFAILEDThe nonce was already used — retry creates a new nonce
insufficient fundsFAILEDFund the source wallet with enough QC for amount + gas
signature verification failedSIGN_FAILEDVerify you’re signing the correct hash with the correct key