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
CREATED
CREATED
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.PENDING_APPROVAL
PENDING_APPROVAL
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.requiredAPPROVED
APPROVED
An approver has approved the transaction. It immediately transitions to
SIGNING_REQUESTED.Webhook event: approval.decisionSIGNING_REQUESTED
SIGNING_REQUESTED
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.SIGNED
SIGNED
The signature has been validated. The service is assembling the raw signed transaction.
BROADCASTING
BROADCASTING
The signed transaction is being sent to the Quantum Chain network via RPC.
CONFIRMING
CONFIRMING
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
COMPLETED
COMPLETED
The transaction has reached the required confirmation depth. The
chainTxHash, blockNumber, and confirmedAt fields are populated.Webhook event: transaction.completedREJECTED
REJECTED
A policy denied the transaction (e.g., amount exceeds limit, destination blacklisted) or an approver explicitly rejected it. Check
failureReason for details.FAILED
FAILED
The transaction failed during broadcast (node rejected it) or during confirmation (reverted on-chain, dropped from mempool). Check
failureReason.Webhook event: transaction.failedCANCELLED
CANCELLED
The transaction was cancelled by the integrator or by timeout before reaching a final on-chain state.
SIGN_FAILED
SIGN_FAILED
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:| Step | State | Who | Duration |
|---|---|---|---|
| 1 | CREATED | API caller | Instant |
| 2 | SIGNING_REQUESTED | Auto (policy engine) | < 100ms |
| 3 | SIGNED | External signer | Depends on your infra |
| 4 | BROADCASTING | Custody service | < 1s |
| 5 | CONFIRMING | Custody service | Network block time |
| 6 | COMPLETED | Custody service | Confirmation depth reached |
Polling vs. webhooks
You can track transaction status two ways:Polling
Call
GET /transactions/{id} periodically. Simple but adds latency and load.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:Error handling
Common failure reasons:| Reason | State | Resolution |
|---|---|---|
amount exceeds max | REJECTED | Lower the amount or adjust the MAX_AMOUNT policy |
destination is blacklisted | REJECTED | Remove the address from the blacklist policy |
nonce too low | FAILED | The nonce was already used — retry creates a new nonce |
insufficient funds | FAILED | Fund the source wallet with enough QC for amount + gas |
signature verification failed | SIGN_FAILED | Verify you’re signing the correct hash with the correct key |