Custody provider integration

Crypto custody & compliance · engineering reference

Custody provider integration

A provider should be mapped into your model—not allowed to become it. Your immutable transaction ID, policy decision, ledger, polling reconciler, and exit package remain authoritative.

VOLATILE CLAIMS: DATE-TAGGEDPRINT: LANDSCAPE TABLES

Start here if this is new

A custody platform sells you signing, key storage, and chain coverage. It does not sell you a wallet system. This sheet is about the seam between their API and your ledger, which is where the integration bugs live.

What does buying a custody platform actually remove?

Threshold-signing implementation and its audit burden, HSM and enclave operations, and the pace of adding new chains. It removes approximately none of the ledger, deposit detection, reconciliation, nonce management, compliance placement, or the withdrawal pipeline.

What do integrators get wrong first?

Trusting webhooks. They arrive at least once, out of order, and sometimes not at all. Treat a webhook as a hint that something changed, and let a polling reconciler be the thing that decides what is true.

Why does every page insist on an idempotency key?

A create call that times out has an unknown outcome. It may already have been accepted. Without a key on the request, your retry is a second withdrawal.

What is a co-signer callback for?

It is the one place your own code sits inside the signing path. It lets you check that the transaction being signed matches the intent your ledger recorded, which is the only real defence against a compromised caller holding valid credentials.

If you remember one lineThe webhook is a hint. The reconciler is the source of truth.

Webhooks are hints; reconciliation is truth

A provider should be mapped into your model, not allowed to become it. These cardinalities are the anti-corruption layer.

Lifecycle6intent → reconcile
Internal intentsexactly 1your ID is authoritative
API attempts0..none idempotency key
Chain hashes0..nreplacement, RBF
Ledger outcomeexactly 1the reconciler decides
Webhookshintsnever truth
01 · INTENT

Persist internal ID and ledger hold.

02 · CREATE

Send external/idempotency ID once.

03 · AUTHORIZE

Provider policy + independent callback.

04 · SIGN

Exact decoded payload only.

05 · OBSERVE

Webhook v2 into durable queue.

06 · RECONCILE

Poll provider + chain; settle actuals.

one internal intent → 0..n API attempts → 0..n chain hashes → exactly one final ledger outcome

One intent, many attempts, one outcome

The cardinality mismatch between your ledger and a provider API is where duplicate and lost withdrawals live.

One internal intent fans out to many attempts and hashes, then back to one ledger outcome A single internal transfer intent may produce any number of provider API attempts and any number of chain transaction hashes, but must always reconcile to exactly one final ledger outcome. Intentyour ID API attemptAPI attemptAPI attempt chain hashchain hash One outcomeledger truth exactly 10..n 0..nexactly 1 Webhooks are hints; the polling reconciler is truth
Many hashes may map to one intent. Only the reconciler closes an intent, and only once.

Generic provider object map

Vendor terminology is dated August 2026. Build an anti-corruption layer so your ledger and state machine do not inherit vendor names.

Generic conceptFireblocksBitGoCoinbase PrimeBuild invariant
Custody containerVault account / asset walletWallet / enterprisePortfolio / wallet / address groupYour internal account ID remains authoritative
Transfer intentTransaction + externalTxIdTransfer / send requestOnchain transactionClient idempotency ID before API call
PolicyPolicies / TAP terminologyWallet/enterprise policy + webhook rulePortfolio/user controlsDefault deny in your domain too
Signer hookAPI Co-Signer callbackWebhook policy / signing architecturePlatform-managed API flowIndependent payload-vs-ledger validation
EventWebhook v2 transaction eventsWallet/transfer webhooksWebSocket/REST stateHint only; reconciler is truth
Chain evidencetxHash + status/subStatusTransfer hash/stateTransaction stateMany hashes may map to one intent

Authentication, idempotency & webhooks

Separate credentials

Read, create, approve/sign, policy-admin, and workspace-admin capabilities belong in different keys and stores.

Concrete use: A reconciler key can list transactions but cannot create or approve one.

Failure mode: One omnipotent key turns a reporting host into a withdrawal host.

Signed request

Bind method, path, body hash, nonce/time, and short expiry to the caller identity.

Concrete use: Hash raw JSON bytes, issue JWT jti=req_8291, and reject clock skew outside the documented window.

Failure mode: Re-serializing JSON can change the signed bytes.

Idempotent create

Generate and persist the client external ID before the first network attempt.

Concrete use: Retry wd_8291 after timeout and query by the same ID before any second create.

Failure mode: A timeout has unknown outcome; blind retry can pay twice.

Raw-body verification

Verify webhook signature and timestamp over exact received bytes before parsing.

Concrete use: Queue the verified envelope, return 2xx quickly, then process asynchronously.

Failure mode: Signing parsed JSON breaks when whitespace/key order changes.

At-least-once consumer

Deduplicate event IDs, tolerate reordering, and compare polled authoritative state.

Concrete use: A late PENDING_SIGNATURE event cannot regress a locally reconciled COMPLETED transaction.

Failure mode: Webhook arrival order is not transaction order.

Silence alarm

No events can mean no activity or a broken integration; only polling distinguishes them.

Concrete use: Poll every 60 seconds and alert when webhook silence exceeds 5 minutes while state changes exist.

Failure mode: A green HTTP endpoint does not prove events are arriving.

Fireblocks transaction lifecycle

Fireblocks primary status and sub-status enums change. Parse unknown values safely, retain raw payload, and alert instead of crashing or treating unknown as success.

Status familyMeaningTerminal?Ledger actionOperator focus
SUBMITTED / PENDING_*Created; policy/approval/signing work remainsNoReserve/hold onlyRead exact current status and subStatus
QUEUEDWaiting for processing/resourceNoKeep holdQueue age and dependency
BROADCASTINGSubmission in progressNoKeep hold; no settlementUnknown hash/outcome window
CONFIRMINGOn chain, confirmation policy pendingNoAttach hash/versionReorg and replacement
COMPLETEDProvider completion criteria metYes for provider flowSettle actual amount/fee after reconciliationVerify chain evidence
BLOCKEDPolicy/compliance stoppedYes unless new intentRelease/retain hold per caseRule number / sanctions context
REJECTEDUser, AML, or workflow rejectionYesRelease holdSub-status is the explanation
CANCELLEDCancelled before completionYesRelease only after chain queryMay have prior hash/state
FAILEDProvider/chain operation failedYes for this attemptReconcile; compensating entryFee, nonce, connectivity, authorization

Failure sub-status operations

These are documented Fireblocks examples as read August 2026. Always store unknown sub-status strings verbatim.

Sub-statusLikely domainResponse
BLOCKED_BY_POLICYPolicy order / sanctionsRecord rule, do not mutate policy to force retry
AUTHORIZATION_FAILEDAPI Co-Signer callbackCheck authentication and callback availability
REJECTED_AML_SCREENINGComplianceFreeze/route case; preserve provider evidence
AUTO_FREEZETransaction screeningTreat as blocked-property workflow
ADDRESS_WHITELISTING_SUSPENDEDDestination activationWait for configured activation; never bypass
ACTUAL_FEE_TOO_HIGHFee policy / marketRe-estimate within ceiling; create linked attempt
AMOUNT_TOO_SMALLDust/minimum/net feeReject before provider call using adapter limits
3RD_PARTY_PROCESSINGExchange/network dependencyPoll provider and third party; do not duplicate
ON_PREMISE_CONNECTIVITY_ERRORSelf-hosted componentFail closed and execute co-signer failover

Policy callback & gas station

Independent callback

Your code compares decoded vendor request to the immutable ledger intent before allowing the co-signer share to act.

Concrete use: Require exact chain, source, destination, asset, amount, fee ceiling, case ID, and policy version.

Failure mode: If callback and transaction creator share one credential/host, the independence is cosmetic.

Critical-path availability

Callback uptime becomes withdrawal signing uptime, so failure must be observable and closed.

Concrete use: Deploy across two fault domains, use bounded timeouts, and test that timeout rejects rather than approves.

Failure mode: A fail-open callback nullifies the strongest integration control.

Gas Station

Fireblocks can auto-fuel EVM vault accounts so token sweeps have native gas.

Concrete use: Enable autoFuel only on intended deposit vaults; monitor source balance and unexpected funding volume.

Failure mode: The gas source can run dry exactly when sweep backlog peaks.

Exit package

Provider disappearance is a designed failure, not a contract footnote.

Concrete use: Restore keys/shares, derivation data, policies, address inventory, pending transactions, and audit evidence in isolation.

Failure mode: Key export without path and address metadata may be unusable.

Platform due-diligence register

No ranking or pricing. Capabilities are product/configuration specific and must be reverified in public docs and contracts; absence from a public page is not evidence of absence.

PlatformPublic integration surfaceCustody/signing model to verifyPolicy/signer hookExit question
FireblocksREST/SDK + Webhooks v2MPC-CMP / workspace configurationPolicies + API Co-Signer callbackCan customer reshare/export each wallet class?
BitGoREST SDK + webhooksMultisig/TSS by productWallet policies + webhook policyWhich keys and metadata remain customer-held?
Coinbase PrimeREST/FIX/WebSocketQualified-custody/platform pathsPortfolio/user controlsHow are wallets and transaction history exported?
CopperPublic docs/API where availableMPC/custody product-specificVerify documented policy hookIndependent recovery/export package?
Anchorage DigitalPublic API/docs where availableInstitutional custodianVerify programmatic approval surfaceAsset return and history format?
DfnsAPI + webhooksWallet-as-a-service MPCPolicy/approval surfaceKey export/reshare per network?
TurnkeyAPI + policy engineSecure enclave / sub-organization modelSigned policy operationsRoot quorum and export path?
SafeSmart-contract accounts + SDKOn-chain owner thresholdModules/guardsCan owners operate without hosted service?
Self-buildYour APIChosen open protocol + HSM/enclaveYour policy/callbackYou own every audit and chain adapter

Go-live integration checklist

Buying signing infrastructure does not buy your ledger, reconciler, compliance placement, nonce allocator, support operation, or exit plan.

  1. Persist client idempotency ID before every create call and enforce database uniqueness.
  2. Separate read, create, approve/sign, policy-admin, and workspace-admin credentials.
  3. Verify request/webhook signatures over exact bytes; enforce timestamp and replay window.
  4. Acknowledge events quickly, queue durably, deduplicate, and tolerate reordering.
  5. Poll authoritative transaction state and chain evidence on a fixed cadence.
  6. Handle unknown status/sub-status values without treating them as success.
  7. Default deny in provider policy and in your own pre-sign callback.
  8. Test callback timeout, authentication failure, regional outage, and failover.
  9. Alert on webhook silence, stuck state age, queue depth, gas-source balance, and reconciler lag.
  10. Execute a full export/exit restore, including paths, addresses, pending state and audit history.
  11. Reconcile provider balances, chain balances, in-flight transfers, fees and ledger liabilities to zero.
  12. Write stuck, rejected, replaced, reorged and provider-unavailable runbooks before customer funds.

Common mistakes & anti-patterns

Failures that pass a vendor demo. Expand each for the control and the reason it fails.

Webhook as truth

Delivery can duplicate, reorder, delay, or disappear.

Concrete use: Poll and reconcile.

Failure mode: Exactly-once is an application invariant, not a transport guarantee.

Parsed-body signature

The verified bytes differ from received bytes.

Concrete use: Capture raw body first.

Failure mode: Framework middleware often destroys the evidence.

Status without sub-status

The actionable reason is discarded.

Concrete use: Persist and route both.

Failure mode: “Failed” cannot tell fee from policy from co-signer outage.

Untested export

Contractual capability may not restore a live wallet.

Concrete use: Execute isolated exit annually.

Failure mode: Vendor failure is the worst time to discover format gaps.

Primary sources & scope

Operational and volatile claims were checked against these first-party documents on 2026-08-31. Examples are illustrative controls, not legal, investment, or vendor-selection advice.