Skip to main content
Fireblocks Flow is an enterprise feature. Contact us to enable it for your environment.
This guide builds the payer’s side of a Flow payment: the page that loads a flow, lets the payer choose how to pay, and shows the result. Each section maps to a screen or a concern of that page. The examples call the client functions from @dynamic-labs-sdk/client, which work with any framework, and show the matching React hooks from @dynamic-labs-sdk/react-hooks next to them. Complete Get started first. Your server creates the flow and passes its ID to this page, for example as a flowId URL parameter, so the payer can reload the page and resume.

Load and route by phase

Load the flow before you show anything, and choose the screen from its current state instead of from what the page did last. A flow has three independent states: executionState tracks the payer’s payment, settlementState tracks delivery to your destination, and riskState tracks screening. Payment lifecycle and states defines each one. Collapse the states into a phase, checking the conditions in this order:
A flow keeps changing without any action from the page once the payment is on its way, and while an exchange payment is waiting for the exchange. shouldPollFlow tells you when to read it again. It returns false once the flow has ended, and while the flow waits for the payer in initiated or source_attached, because the functions the page calls return the updated flow.
useGetFlow already polls, so do not pass your own refetchInterval. It keeps the newest flow it has seen, keeps showing it when a later read fails, and does not retry an error such as FlowNotFoundError that cannot succeed. The mutation hooks in this guide write the flow they return into the same cache, so every component that calls useGetFlow for the flow re-renders with it. To run code when a state changes, use useSubscribeToFlowStateChange from the React hooks catalog, which polls the same way.

Choose a payment method

In the pay phase with executionState initiated, let the payer choose a payment source: a wallet, an exchange, or a deposit address. Payment sources describes each one and which are screened.
uiConfig.enabledPaymentMethods lists the sources your server chose to show when it created the flow. It is for display only, and the API does not enforce it: a payer can still attach another source with direct requests, so do not rely on it for a compliance rule. The rest of uiConfig is also display data for your UI. Show merchantName and merchantLogoUrl if your server set them, and when termsOfServicesUrl is set, ask the payer to accept the terms before they confirm. The payer can change their mind until the payment is sent. Attaching another source replaces the current one, so a back button on any pay screen returns to this choice.

Wallet

A wallet payment takes four steps: connect a wallet, attach it, quote a token, and approve the payment in the wallet.

Connect and attach the wallet

Connect the payer’s wallet with your wallet connection UI. Build a wallet picker and Connect and verify a wallet cover it, and Compliance describes checks your app can run at this point, such as verifying wallet ownership. Then attach the wallet. Pass the wallet’s address, its chain family, and the network it is on. fromChainId is required for wallet sources, and the request fails without it.
A blocked wallet stays blocked, so do not retry it. Screening explains what is screened and when.

Quote a token

List the tokens the payer holds on the wallet’s network with getTokenBalances, and let them pick one. Filtering spam tokens from that list is one of the compliance checks your app can run. When the payer picks a token, quote it:
The quote can fail for reasons the payer can act on. Handle Flow errors shows how to read FlowQuoteUnavailableError.quoteFailures, which says why each of your settlement tokens could not be quoted, for example an amount below the route’s minimum.

Review the quote

Show the payer what they send, the fees, and how long the quote lasts. Quote amounts are integer strings in the token’s smallest unit, so format them with the token’s decimals from fromTokenInfo and toTokenInfo:
A wallet quote is valid for 60 seconds. Show a countdown to quote.expiresAt, and when it runs out, replace the confirm button with one that gets a new quote. A new quote for the same token reuses the quote’s chain and token:
In deposit mode, also pass the payer’s amount, as described in Deposit mode amount entry.

Send the payment

When the payer confirms, call submitFlowTransaction. It prepares the transaction, switches the wallet to the right network, asks the payer to approve a token allowance when an ERC-20 payment needs one, asks them to sign and send the transaction, and reports it to the flow. onStepChange reports approval and transaction, so you can show which prompt the wallet is waiting for. Treat any successful return as sent. When the wallet accepted the transaction but did not return its hash in time, submitFlowTransaction returns the flow as it is, still in signing, without reporting a hash. The transaction may still be on its way, so treat that return as sent too. Either way, move to the processing screen and do not offer the confirm button again. Handle errors by class, and never by message text:
Retry only in two cases: after UserRejectedError, by submitting again, and after the quote_expired code, by getting a new quote and submitting it. After any other error, call getFlow and show the screen for the state it returns. A flow still in quoted was not sent, and the payer can confirm again. A flow in signing or later may have been sent, and the page shows it as processing. A second approval could send a second payment.

Deposit address

With a deposit address, the payer sends funds from any wallet or exchange to an address generated for the payment. Nothing is connected or signed in your UI.

Attach the source and get the address

Ask the payer for the chain family, network, and token they will send, and for a refund address on that chain family. Deposit addresses support EVM chains, Solana, Bitcoin, and TRON. Then attach the source and quote it. The quote returns the deposit address.
The refund address is where the deposit provider returns funds it does not accept. Dynamic does not initiate refunds, and a refund can fail, as Failures and refunds explains. Pass fromTokenAddress for the token the payer will send: without it, the quote uses the chain’s native token.

Show the address

Show the deposit details only when they belong to the payer’s current selection, so a stale address never stays on screen while a new quote loads:
Tell the payer to send exactly the amount shown, on the chain and in the token they selected. To let a wallet fill in both the address and the amount, encode them in a QR code with the chain’s payment URI:
Wallets differ in which URI fields they read, so always show the address and amount as text the payer can copy too.

Refresh the quote

The deposit address stays open until the flow ends, but the deposit provider guarantees the quoted amount for only 60 seconds. The quote’s expiresAt shows the flow’s expiry, not that 60-second window, so time the refresh from quote.createdAt. If the payer has not sent funds within 60 seconds, get a new quote for the same chain and token:
Cancel the timer when the flow leaves quoted, because a refresh is no longer allowed once the deposit is detected. A new quote for the same chain and token usually keeps the same address. If the address changes, show the new one. Funds sent later to the address can still arrive, but the amount delivered can differ from the original quote. In deposit mode, pass the payer’s amount on every refresh.

Wait for the deposit

The flow is polled while it is quoted, so the load section picks up the deposit. When the deposit is seen, the flow moves to source_detected and then to source_confirmed. From source_detected, the payer can no longer change the source, so hide the back button and show that the payment was received and is being confirmed. If the payer switches to another chain or token, attach and quote again, which gives a new address. Funds already sent to an earlier address for the same flow can still complete the payment, so tell the payer which address is current and that funds already sent may still count.

Exchange

With an exchange source, the payer pays from an exchange account through a purchase link, and the exchange confirms the payment. This section uses Coinbase as the example. Each exchange must be enabled for your environment in the Dynamic Console, and an exchange is available for a flow only when the flow settles in a token and on a network that exchange supports. For Coinbase, that is USDC.

List the exchanges

Ask the flow which exchanges it supports, and show each one by label and iconUrl:
When the payer picks an exchange, attach it with the exchange’s key. The returned flow carries the purchase link in exchangeSource.metadata.url.
Open the link with window.open inside the click handler of an “Open” button, as the React example does. Browsers can block a window that opens after an await, so do not open it at the end of the attach call. The FlowExchangeSourceError codes tell you why an exchange cannot be used, and Handle Flow errors lists them.

Wait for the exchange

After the payer opens the link, show a waiting screen and keep reading the flow. A flow in source_attached with an exchange source is polled, so the load section picks up the change. The exchange confirms the payment, and the flow moves to source_confirmed and then to settlement completed. Do not call broadcastFlow for an exchange payment. The exchange reports the payment, and the page only reads the flow. To let the payer open the exchange again, attach the exchange again for a new link. A purchase already completed through an earlier link can still reach your destination, so warn the payer before they start a second purchase.

Processing

In the processing phase, the payment is on its way and the payer has nothing left to do. Keep reading the flow and show progress. Hide the back and cancel buttons. An exchange payment waits for the exchange in source_attached, which is still in the pay phase, so the waiting screen from the exchange section covers that step. Do not show success at source_confirmed. It means the payer’s funds arrived, but delivery to your destination can still fail. The payment is done only when settlementState is completed.

Outcomes

Show the final screen for the phase, and decide fulfillment on your server from webhooks or a server read of the flow, not from this page. A failed, cancelled, or expired flow is final. The same flow cannot be paid again, so a new attempt needs a new flow from your server. Failures and refunds lists the failure codes and what to show for each. On success, show the amount that was actually delivered with getFlowDeliveredAmount. It returns undefined when settlement did not report an amount, so fall back to the quoted amount, and to the flow’s requested amount when there is no quote:
The mode decides whether the delivered amount can be smaller than requested. In deposit mode, a short deposit is accepted and settles for what arrived, so show and credit the delivered amount. In payment mode, a short payment is not accepted, so a completed flow was paid in full. Payment lifecycle and states explains each amount. If your server set uiConfig.returnUrl, you can send the payer back to your app from the final screen.

Deposit mode amount entry

In deposit mode, the flow’s amount is a suggestion, and the payer can choose how much to deposit. Show an amount field only when flow.mode is deposit; other modes reject an amount with FlowQuoteAmountError code amount_not_editable. The payer can enter the amount in USD, or in the token they pay with as SOURCE_TOKEN, where "0.5" means 0.5 of that token. Pass amount and amountCurrency on every quote, including refreshes. A quote without them prices the flow’s current amount in USD, so a token amount would not stay fixed. Pass both to submitFlowTransaction too, because it gets a new quote with them after the payer declines in the wallet.
A new amount replaces flow.amount. When the source is a wallet, changing the amount screens the wallet again, so the quote can fail with FlowSourceBlockedError even though the attach succeeded. Show amounts with flow.currencyTokenInfo when the flow is denominated in a token, and with the quote’s fromTokenInfo and toTokenInfo otherwise.

Cancel

The payer can cancel a flow in source_attached, quoted, or signing with cancelFlow. Cancelling needs the session token that attaching a source stored, so a flow in initiated cannot be cancelled from the page; your server can cancel it instead, as described in the Flow payments API guide. Show the cancel button only in those three states, and hide it as soon as the payer approves a wallet payment.
A cancelled flow is final. Do not cancel after the payer declines in the wallet: the SDK already prepared a new quote, so the payer can try again or pick another way to pay on the same flow.

Expiry timer

A flow expires at flow.expiresAt if the payment has not been sent by then. The flow is not read again while it waits for the payer in initiated or source_attached, so the page does not see the expiry until the next action fails. Run a timer on expiresAt and read the flow when it fires:
Expiry applies only before the payment is sent. A flow in broadcasted or later does not expire, and the page keeps showing progress. An action on an expired flow fails, with FlowStateConflictError for attaching a source and with FlowSessionExpiredError for the actions that need the session token, because an expired flow’s token stops working. In both cases, read the flow and show the expired screen. Expiry and cancellation describes flow and quote expiry.

Reload and another device

On every load, read the flow and route by phase, as the load section does. Do not repeat the last action just because the page reloaded. When the payer attaches a source, the SDK stores the flow’s session token on the device, and every later action sends it. Reading the flow needs only the flow ID.
  • Same browser or app. The token is still stored, so the payer resumes from the flow’s state. A wallet payment in quoted can resume at the review screen when the same wallet is connected, matching fromAddress and fromChainName. A deposit-address payment can restore the selection from fromChainId and fromToken. An exchange payment in source_attached shows the waiting screen, and opening the exchange again needs a new link.
  • Another device, or cleared storage. The page can read the flow but has no token, so the next action fails with FlowSessionTokenMissingError. A token that the server no longer accepts fails with FlowSessionExpiredError instead, and the SDK removes it.
Both errors recover the same way. Read the flow, and if the payment has not been sent, attach the source again. Attaching issues a new token on this device, and the token on the other device stops working. After the payment is sent, there is nothing left for the payer to do, and the page only shows progress.
A flow in signing on another device cannot be attached again without the original token, because the payer may already have approved the payment on the first device. Show it as processing. Payment lifecycle and states explains why recovery works this way.

State-to-screen table

Settlement completed shows success, and settlement failed shows the failed screen, whatever the execution state. Until the payment is sent, the payer can also go back and attach another source, or cancel.
Last modified on October 29, 2026