This is an enterprise-only feature. Please contact us to enable.
What We’re Building
A server-side payment flow built on Fireblocks Flow. Fireblocks Flow lets you accept a crypto payment from any supported chain, wallet, or exchange and have it settled in a specific token on a specific chain. This guide uses the Flow API directly. By the end of this guide you will be able to:- Create a flow from your backend, specifying the mode, amount, settlement, and destination
- Walk through the full flow: attach source, get quote, sign, broadcast
- Send withdrawals from your treasury or vault to end-user wallets (API only)
- Poll or receive webhooks for settlement completion
- Handle errors and edge cases
fetch. For an overview of what Fireblocks Flow can do — funding sources, settlement currencies, compliance, and webhooks — see the Fireblocks Flow overview.
Prerequisites
- A Dynamic environment with Fireblocks Flow enabled
- An environment API token (
dyn_...) withflow.writescope from Developer > API Tokens in the dashboard - A connected wallet that can sign transactions on the chains you support. If your app doesn’t already handle wallet connection, use the Dynamic JavaScript SDK to connect a wallet and sign
- Node.js 18+ (or any runtime with
fetch)
Base URL
Overview
The flow is a state machine with seven sequential steps. The same seven steps are used in payment, deposit, and withdrawal flows — only the mode, how the amount is interpreted, and who acts as source vs destination change between them. This guide walks through all three flows end-to-end:409.
Resume and retry by state
A flow ID represents one execution attempt. Before retrying a request or restoring an interrupted session, fetch the flow and choose the next action from its currentexecutionState.
Jump to the flow that matches your use case:
- Payment flow — receiver fixes the amount
- Deposit flow — sender chooses the amount
- Withdrawal flow — platform sends funds to an end-user wallet (API only)
Authentication
The session token is returned once when you attach a source (Step 2). Store it for the duration of the flow.
Choose Your Flow
This guide covers three use cases. The seven steps are the same in each — what changes is themode you pass in Step 1, how the amount is interpreted, and which wallet is source vs destination. Pick the flow that matches your use case:
- Payment flow —
mode: "payment". The receiver fixes the amount on each transaction (e.g., a $25 invoice). Use for invoices, e-commerce checkouts, or paid services. - Deposit flow —
mode: "deposit". The sender chooses how much to send (e.g., a $100 top-up). Use for funding flows, on-ramps, or open-ended deposits. - Withdrawal flow —
mode: "withdraw". Your platform fixes the payout amount and signs from a treasury or vault; the end user receives funds at an address you pass per flow. HTTP API only — not available through the JavaScript SDK.
Shared concepts
These apply to all flows:Security model: The API key (
dyn_... with flow.write scope) is required only to create a flow. Creation is where amount, currency, destination, and settlement are fixed — so gating it behind the API key means a browser client has no endpoint that accepts those fields. Everything after creation is driven by a capability session token (dft_...) minted at source attachment.Payment Flow
A complete walkthrough formode: "payment", where the receiver fixes the amount the sender must pay.
Step 1 (Payment): Create a Flow
Create a flow from your backend. This is a server-side call authenticated by your API token withflow.write scope. The body specifies the amount, currency, settlement tokens, and destination wallets — these are fixed at creation time and cannot be changed later.
Response (201):
Step 2 (Payment): Attach Source
Declare which wallet and chain the payer is paying from. This step returns a session token (dft_...) that authenticates all subsequent calls, and starts risk screening asynchronously.
For a Solana payer, use the Solana chain values and address format:
sessionToken — it is returned once and authenticates all subsequent calls for this flow.
Error (403): Blocked by sanctions screening — cancel and retry with a different source.
Step 3 (Payment): Get a Quote
Specify which token the sender is paying with. The API finds the best route to the flow’s settlement token(s) for the receiver’s requested amount.
Response (200):
toAmount matches the receiver’s requested amount and fromAmount is what the sender must pay (including swap costs and fees).
Quotes expire in 60 seconds. If it expires before you call
/prepare, request a new quote. The quoteVersion increments with each new quote.Step 4 (Payment): Prepare Signing
Locks in the quote and returns the signing payload. You can optionally request on-chain balance assertions.
Response (200): Transaction with
executionState: "signing" and quote.signingPayload:
Possible errors:
422— Quote expired: go back to Step 3422— Risk not cleared: pollGET /flow/{flowId}untilriskStateis"cleared", then retry422— Insufficient balance: response includesrequiredandavailableamounts
Step 5 (Payment): Sign and Broadcast On-Chain
Useprepared.quote.signingPayload to sign and submit the transaction with whatever wallet your sender has connected. The payload — and the code to sign it — depends on the source chain. See Signing the transaction by chain for EVM, Solana, Sui, and Bitcoin examples. It returns a txHash you’ll report in Step 6.
Step 6 (Payment): Notify Backend of Broadcast
This endpoint does not broadcast the transaction on-chain. That already happened in Step 5 — when the wallet signed and submitted the transaction, the network received it. This call is your client notifying Dynamic’s backend that the broadcast happened and handing over the resulting
txHash, so the backend can start watching the chain for confirmation and orchestrating settlement.executionState: "broadcasted".
Step 7 (Payment): Wait for Settlement
After broadcast, the backend handles cross-chain settlement automatically. Monitor progress via polling or webhooks.Option A: Polling
Settlement progresses through:
none → routing → bridging → swapping → settling → completed. Same-chain, same-token payments jump directly to completed.
Option B: Webhooks (Recommended)
Set up a webhook to receive events as the transaction progresses:Example: flow.settlement.updated payload
webhook-handler.ts
Deposit Flow
A complete walkthrough formode: "deposit", where the sender chooses how much to send. The seven steps mirror the payment flow — what changes is the mode and how the amount is interpreted.
Step 1 (Deposit): Create a Flow
Create a deposit flow from your backend. The body specifies the deposit amount, settlement tokens, and destination wallets.
Response (201):
Step 2 (Deposit): Attach Source
Declare which wallet and chain the depositor is funding from. This step returns a session token (dft_...) that authenticates all subsequent calls, and starts risk screening asynchronously.
For a Solana depositor, use the Solana chain values and address format:
sessionToken — it is returned once and authenticates all subsequent calls for this flow.
Error (403): Blocked by sanctions screening — cancel and retry with a different source.
Step 3 (Deposit): Get a Quote
Specify which token the depositor is funding with. The API finds the best route from that token to the flow’s settlement token(s) for the deposit amount.
Response (200):
fromAmount is what the depositor’s wallet will be charged in the source token; toAmount is what lands at the destination after swap and fees.
Quotes expire in 60 seconds. If it expires before you call
/prepare, request a new quote. The quoteVersion increments with each new quote.Step 4 (Deposit): Prepare Signing
Locks in the quote and returns the signing payload. You can optionally request on-chain balance assertions.
Response (200): Transaction with
executionState: "signing" and quote.signingPayload:
Possible errors:
422— Quote expired: go back to Step 3422— Risk not cleared: pollGET /flow/{flowId}untilriskStateis"cleared", then retry422— Insufficient balance: response includesrequiredandavailableamounts
Step 5 (Deposit): Sign and Broadcast On-Chain
Useprepared.quote.signingPayload to sign and submit the transaction with whatever wallet the depositor has connected. The payload — and the code to sign it — depends on the source chain. See Signing the transaction by chain for EVM, Solana, Sui, and Bitcoin examples. It returns a txHash you’ll report in Step 6.
Step 6 (Deposit): Notify Backend of Broadcast
This endpoint does not broadcast the transaction on-chain. That already happened in Step 5 — when the wallet signed and submitted the transaction, the network received it. This call is your client notifying Dynamic’s backend that the broadcast happened and handing over the resulting
txHash, so the backend can start watching the chain for confirmation and orchestrating settlement.executionState: "broadcasted".
Step 7 (Deposit): Wait for Settlement
After broadcast, the backend handles cross-chain settlement automatically. Monitor progress via polling or webhooks.Option A: Polling
Settlement progresses through:
none → routing → bridging → swapping → settling → completed. Same-chain, same-token deposits jump directly to completed.
Option B: Webhooks (Recommended)
Set up a webhook to receive events as the transaction progresses:data.newState to decide what action to take. See the payment webhook section for the full payload shape.
Key states to handle:
webhook-handler.ts
Deposit Address Flow
A deposit address flow generates a unique address per transaction. The user sends funds to that address directly — no wallet connection or on-chain signing is required on your end. The inbound transfer is detected automatically and the flow advances. Use this when your users are paying from a CEX, a cold wallet, or any context where connecting a Web3 wallet is not practical. The deposit address can also be rendered as a QR code for users to scan from a mobile wallet. Supported chains: BTC, SOL, EVM, and TRON.How it differs from the wallet flow
Step 3 (Deposit address): Attach Source
refundAddress to specify where to return funds if the transfer fails. Omit to refund to the original sender.
executionState: "source_attached".
Step 4 (Deposit address): Get a Quote
fromTokenAddress for native tokens (BTC, SOL, or EVM native).
Response (200):
quote.depositAddress to the user. They send quote.fromAmount (in the token’s base unit) to that address from any wallet or exchange.
Step 5 (Deposit address): Wait for transfer detection
Steps 5–7 (prepare, sign, broadcast) do not exist for deposit address flows. Once the user sends funds, the transfer is detected automatically and the flow advances. PollGET /sdk/{environmentId}/transactions/{transactionId} every few seconds until executionState leaves "quoted":
Deposit addresses expire after 48 hours. If the flow reaches
"failed" with no funds received, create a new transaction.
Withdrawal Flow
Withdrawals and money-out are supported through this HTTP API only. The Fireblocks Flow JavaScript SDK guide covers payment and deposit flows, not platform-to-user payouts.
mode: "withdraw" and reversed roles:
Each withdrawal flow specifies the end user’s destination directly in the flow creation body. See Money in and money out on the overview for the product model.
Step 1 (Withdrawal): Create a Flow
Create a withdrawal flow from your backend with the payout amount and the end user’s destination wallet.
Response (201): Same shape as the deposit flow Step 1. Store
flow.id as your flowId.
Step 2 (Withdrawal): Attach Source
Attach your treasury, vault, or server wallet as the funding source — not the end user’s wallet.executionState: "source_attached". Store the sessionToken — it authenticates all subsequent calls.
Error (403): Blocked by sanctions screening — cancel and retry with a different source.
Steps 3–7 (Withdrawal): Quote Through Settlement
Steps 3–7 use the same endpoints as the deposit flow. Differences are who signs and what you do on completion:
Example — Step 3 quote (treasury pays with USDC on Base; user receives USDC on Ethereum):
settlementState === "completed" means funds reached the destination from Step 1. Handle flow.settlement.updated webhooks (where data.newState === "completed") the same way as in Step 7 (Deposit), but credit the user’s balance or close the withdrawal request instead of treating it as an inbound deposit.
Signing the Transaction by Chain
Step 5 of every flow signs and broadcastsprepared.quote.signingPayload, then reports the resulting txHash in Step 6. The flow itself is identical across chains — only this signing step differs. The payload field depends on the source chain:
Any Dynamic SDK can be used for signing — not just the browser JS SDK. React, React Native, Flutter, Swift, Kotlin, Unity, and the Node.js SDK (server-side MPC wallets) all surface the same signing primitives. See docs.dynamic.xyz for platform-specific guides. The examples below show browser wallet (viem/wagmi) and Node SDK patterns.
prepared is the response from Step 4.
EVM
Uses an already-connected EVM wallet client (walletClient) — for example the one returned by useWalletClient() in wagmi or by Dynamic’s primary wallet connector. If evmApproval is present, send the approval transaction first, then the main one.
sign-evm.ts
EVM — Node SDK (server-side MPC wallet)
sign-evm-node.ts
Solana
serializedTransaction is a base64-encoded versioned transaction — there are no approvals. Deserialize it, sign with your connected wallet (or keypair), and broadcast. The example below uses @solana/web3.js.
sign-solana.ts
Solana — Node SDK (server-side MPC wallet)
sign-sol-node.ts
Sui
Sui returns the sameserializedTransaction field as Solana — a base64-encoded transaction. Deserialize and sign it with your Sui keypair via @mysten/sui, then report the resulting digest as txHash.
Bitcoin
Bitcoin returns apsbt field — a base64-encoded unsigned PSBT. Sign each input with your wallet, finalize, extract the raw transaction, broadcast it, and report the resulting txid as txHash.
Cancelling a Transaction
Cancel any time before broadcast (states:initiated, source_attached, quoted, signing):
executionState: "cancelled". Once cancelled, create a new transaction to retry.
Fee Collection and Claiming
Charge your own fee on every swap a flow runs. You attach a fee config when you create a flow, and on each swap a percentage of the source amount is collected and delivered to the recipient wallet(s) you specify. This is direct revenue for your business, layered on top of Dynamic’s own routing costs. Depending on the route used to fill a swap, fees are collected one of two ways:- Distributed on-chain at swap time — the fee is sent straight to the wallet address you specified in the fee configs when creating the flow, as part of the settlement transaction. Nothing to claim; it arrives with the swap.
- Accrued as a claimable balance — the fee accumulates off-chain (typically as USDC) and you sweep it later with the claim flow below.
Fee collection is an EVM-only feature. See Supported chains for fees.
Add a fee config
feeConfig is an optional field on the create flow body (Step 1 of any mode — payment, deposit, or withdrawal). It is fixed at creation and cannot be changed later.
Validation. The create call returns
422 if:
- there are fewer than 1 or more than 10 recipients,
- any
percentageis not strictly between0and1, - the recipients’ combined
percentageis1or greater (that would consume the entire swap), or - any
walletAddressis not a valid EVM address (see Supported chains for fees).
percentage × sourceAmount, split independently — so [{ 0.01 }, { 0.005 }] collects 1.5% total and delivers each recipient their own cut.
Supported chains for fees
Fee collection is an EVM-only feature:- Recipient addresses must be EVM (
0x…). A non-EVM recipient (for example a Solana address) is rejected at flow creation with422. Both underlying settlement models require an EVM recipient — on-chain distribution sends to an EVM receiver, and claimable balances are released by an EVM signature from the recipient wallet. - Fees are collected on EVM swaps and any claimable balance settles on an EVM chain, denominated per token and chain in the balances response.
Check claimable balances
Check whether a recipient has any fees waiting to be claimed. Returns a per-token, per-chain breakdown plus ahasClaimableFees convenience flag.
Response (200):
An empty
balances list (hasClaimableFees: false) means there is nothing to claim right now — either no fees have accrued, or every fee was distributed directly on-chain at swap time.recipientAddress is not a valid EVM address.
Claim accrued fees
Claiming is a two-step, sign-in-the-middle flow: initiate to get the payload(s) the recipient must sign, then submit the signatures. The recipient wallet authorizes the release — no provider details are ever exposed. 1. Initiate the claim.
Response (200):
An empty
steps array means there was nothing to claim — no signing or submit needed.
Error (400): recipientAddress is not a valid EVM address.
2. Sign each step with the recipient wallet, then submit the signatures.
Response (200):
After a successful submit, re-check balances — the claimed balance should now be empty.
Example: claim in TypeScript
claim-fees.ts
Sign
data.message exactly as returned — don’t reconstruct or modify it. data.signatureKind (eip191) tells you how to sign: it’s a standard personal-sign message, so most libraries’ signMessage (for example viem’s) produce the right signature directly.Complete Example
A self-contained TypeScript script that creates a flow from the backend, executes the payment lifecycle, and polls for settlement. It assumes you already have a connected wallet client — for example one returned by Dynamic’s JS SDK, wagmi’suseWalletClient(), or any viem WalletClient. If your app doesn’t have wallet connection yet, use the Dynamic JavaScript SDK to provide it.
This example pays from an EVM wallet. To pay from Solana, keep Steps 1–4 and 6–7 identical — only swap the Step 5 signing block for the Solana variant (sign-solana.ts), attach a Solana source (
fromChainName: "SOL", fromChainId: "101"), and quote with a Solana fromTokenAddress (mint, or 11111111111111111111111111111111 for native SOL).flow-example.ts
Supported Chains and Native Tokens
Fireblocks Flow supports the following chains. Use these values forchainName, chainId, and token addresses in your settlement config and source attachment.
Chain Reference
Except for the EVM chains listed below, only mainnet is supported:
Native Token Addresses
For native tokens (ETH, SOL, BTC, SUI, TRX), use any of the accepted addresses below in token address fields:
For non-native tokens, use the token’s contract address on that chain (e.g.,
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 for USDC on Base).
Error Reference
By status code
By step
Step 1 — Create flow
Step 1 — Create flow
Step 2 — Attach source
Step 2 — Attach source
Step 3 — Get quote
Step 3 — Get quote
Quote failure response format:When no quotes are available, the 422 response includes a
quoteFailures array with per-settlement failure details:symbol on chainName/chainId) and why (reason).Quote failure reasons:Step 4 — Prepare signing
Step 4 — Prepare signing
Step 5 — Record broadcast
Step 5 — Record broadcast
Cancel
Cancel
Tips
- Balance assertions: Enable both
assertBalanceForGasCostandassertBalanceForTransferAmountin prepare to catch insufficient balance before signing. - Quote evaluation: Check
quote.fees.totalFeeUsdandquote.estimatedTimeSecprogrammatically before proceeding. Cancel and retry with a different token/chain if fees are too high. - Quote expiry: Quotes last 60 seconds. Sign promptly. You can re-quote multiple times —
quoteVersionincrements with each new quote. - Idempotency: Use the
memofield to store your own idempotency keys (e.g.,{ "orderId": "order_abc123" }). - Error recovery: If your process crashes mid-flow, call
GET /flow/{flowId}to check the currentexecutionStateand resume from the correct step. - Session token lifetime: Matches the flow’s
expiresIn(default 1 hour). - Balance API — required params: Pass
includeNative=trueto include ETH/SOL in the response (omitted by default). For Solana,networkId=101is also required — without it the endpoint returns[]even for funded wallets. For EVM, use the chain ID (e.g.networkId=8453for Base). The indexer can lag 60–90 seconds after a deposit; if a balance check returns empty immediately after funding, wait a moment and retry. - Solana routing: SOL→USDC routes vary by market conditions. The Titan protocol (Pyth oracle–dependent) is common and usually works, but can fail with
PythOracleOutdatedif oracle prices are stale. If you hit this, try adjustingslippage(e.g.0.5) to get an alternative route such as dflow. For agents, cross-chain settlement to Base (SOL → USDC on Base via MayanFinance FastMCTP) avoids oracle dependencies entirely and has zero bridge fees. - Solana simulation: Pre-built Solana transactions from the flow API may fail RPC simulation with oracle errors even though they succeed on validators. Use
skipPreflight: truewhen callingsendRawTransactionto bypass simulation.
Quick Reference
Related
- Fireblocks Flow overview — Product capabilities, modes, withdrawals, and webhooks
- Token swaps via API — Standalone token swaps without flow orchestration
- Fireblocks Flow JavaScript SDK guide — End-to-end flow with the Dynamic JavaScript SDK
attachFlowSource— SDK source attachmentsubmitFlowTransaction— SDK submit (prepare + sign + broadcast)