Skip to main content
This is an enterprise-only feature. Please contact us to enable.
This tutorial builds a payment where the payer sends the exact token you settle in. You create a payment flow with disableSwaps, the flow issues a deposit address on the settlement chain, the payer transfers the token to it, and the funds reach your destination. No swap provider quotes the payment and nothing is converted: the payer sends USDC on Base, and you receive USDC on Base. Use this when your payers already hold the token you want to receive. To accept any token or chain, see the API guide.

Prerequisites

  • A Dynamic environment with Flow payments enabled.
  • An environment API token with the flow.write permission, kept on your server and read from an environment variable or a secrets manager.
  • A destination address on the settlement chain.
The API base URL is https://app.dynamic.xyz/api/v0.

Step 1: Create the flow

Create the flow from your backend with mode: "payment" and disableSwaps: true. The amount, currency, settlement, and destination are fixed here and cannot change later.
The response returns the created flow in flow. Store flow.id: your payer page needs it for the next steps, and your backend uses it and the memo to reconcile the payment later.
With disableSwaps: true, the quote fails with 422 if the payer’s token or chain differs from a configured settlement. List every token and chain you accept in settlementConfig.settlements.
To set how long the payment stays open, pass expiresIn in seconds. The default is 86400 seconds (24 hours).

Step 2: Attach a deposit address source

On your payer page, attach a deposit_address source to the flow. This request needs no credential, and its response carries the flow session token that the remaining requests use.
  • fromChainName and fromChainId must match a chain in your settlementConfig.settlements.
  • refundAddress is required. It is where the deposit provider returns funds it does not accept. Dynamic does not initiate refunds, and a refund can fail.
  • fromAddress is not accepted for a deposit_address source, because the payer does not connect a wallet.
Store sessionToken immediately. It is returned once, with Cache-Control: no-store, and expires at sessionExpiresAt.

Step 3: Get the deposit address

Request a quote with the session token. For a deposit address source, this request issues the deposit address and returns it on the flow.
The response is the flow itself, not wrapped in another object. Pass fromTokenAddress for a token such as USDC; without it, the quote uses the chain’s native token, which does not match a USDC settlement. The flow moves to executionState: "quoted" and includes:
  • depositAddress: the address the payer sends funds to.
  • quote.fromAmount: how much of the token the payer must send, as an integer string in the token’s smallest unit. Divide it by 10 ** quote.fromTokenInfo.decimals to display it.
The quoted amount is valid for 60 seconds. The address stays open until the flow ends, but the deposit provider guarantees the quoted amount for only 60 seconds, and quote.expiresAt shows the flow’s expiry rather than that window. If the payer has not sent funds after 60 seconds, request the quote again to refresh the amount. A new quote for the same chain and token usually keeps the same address.

Step 4: Show the payer the address

Render depositAddress and the amount with the token symbol and chain, so the payer knows exactly what to send and where. A QR code of the address works well for mobile wallets. A deposit address belongs to one flow. Create a new flow for each payment.
A deposit address source has no prepare, sign, or broadcast step. The payer pays with any wallet or exchange that can send the settlement token on the settlement chain.

Step 5: Detect the payment

Flow payments watches the deposit address and advances the flow on its own. Execution moves to source_detected when the transfer is seen and to source_confirmed once it is confirmed. Your backend can learn about it from webhooks, which are recommended, or by polling.

Webhooks

Subscribe your backend to the flow events: See Webhooks for the payload shapes and Webhook setup for endpoint configuration and signature verification.

Polling

For a prototype or a low-volume integration, poll the read endpoint until the flow reaches a final state. It needs no credential, and it returns the flow itself.

Step 6: Confirm the payment

Treat settlementState: "completed" as paid. Because this is a payment flow, completed means the payment was accepted in full: a deposit smaller than the quote does not settle, and the deposit provider attempts to return it to the refund address. Execution reaching source_confirmed only means the payer’s transfer arrived; settlement can still be in progress. When settlement completes, reconcile the payment on your server:
  • Match flowId and the flow’s memo, for example the orderId from Step 1, to your order. Webhooks do not carry memo, so read it from the flow with your API token, as shown in Read a flow from your server.
  • Record settlementTxHash from the flow.settlement.updated event, or from the server read, as the on-chain receipt. The payer’s view of the flow does not include it.
  • Record the flow’s txHash as the payer’s deposit transaction, when the deposit provider reported one.
  • settlement.completedAt is when the funds reached your destination.
If the flow reaches cancelled, expired, or failed, this payment attempt is over. Create a new flow for another attempt. If you do not want to build the payer page, create a payment link instead of calling the create endpoint directly:
The body is the same as Step 1 plus a required baseUrl, the page the link opens, whose origin must be a registered CORS origin for the environment. The response returns a paymentUrl to send to the payer. See Payment links.

Common pitfalls

  • Do not send Authorization: Bearer to /sdk/ endpoints. Only the /server/ endpoints accept the API token. The payer endpoints use X-Dynamic-Flow-Session-Token or no credential.
  • The quote fails with 422. The payer’s fromChainName, fromChainId, or token does not match a configured settlement, so the payment would need a swap while disableSwaps is set.
  • A request returns 409. The flow moved past that step. Read the flow and continue from its current executionState instead of repeating the request. API errors lists the codes.
  • The payer sends the wrong token or chain. The transfer does not match the quote, and the deposit provider decides what happens to it. It may return the funds to the refund address, but Dynamic does not initiate or guarantee a refund. Show the payer the exact token, chain, and amount to avoid this. See Failures and refunds.
  • The payer sends funds after the flow ends. Funds sent to the address of a cancelled or expired flow can still reach your destination without the flow recording them. See Expiry and cancellation.

Next

API guide

Webhooks

Settlements

Last modified on October 28, 2026