> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Checkout with a same-asset transfer

> Build a Flow checkout where the customer pays with a plain transfer of the settlement token, with no swap or conversion.

<Note>
  This is an enterprise-only feature. Please [contact us](https://www.dynamic.xyz/book-a-call) to enable.
</Note>

This tutorial builds a checkout where the customer pays the exact token you settle in. You create a payment flow with `disableSwaps`, Flow issues a deposit address on the settlement chain, the customer transfers the token to it, and Flow forwards the funds to your destination wallet. There is no quote through a swap provider and no conversion: the customer sends USDC on Base, you receive USDC on Base.

Use this when your customers already hold the token you want to receive. To accept any token or chain, see the [Flow API guide](/docs/flow/api) for the full swap flow.

## Prerequisites

* A [Dynamic](https://console.dynamic.xyz) environment with Fireblocks Flow enabled
* An environment API token (`dyn_...`) with `flow.write` scope from **Developer > API Tokens** in the developer console
* A wallet address on the settlement chain where funds should land
* Node.js 18+ (or any runtime with `fetch`)

The API base URL is `https://app.dynamicauth.com/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 be changed later.

```bash theme={"system"}
curl --request POST \
  --url https://app.dynamicauth.com/api/v0/server/<YOUR_ENVIRONMENT_ID>/flow/payment \
  --header 'Authorization: Bearer <YOUR_API_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '{
    "amount": "25.00",
    "currency": "USD",
    "disableSwaps": true,
    "settlementConfig": {
      "strategy": "preferred_order",
      "settlements": [
        {
          "chainName": "EVM",
          "chainId": "8453",
          "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
          "symbol": "USDC",
          "tokenDecimals": 6
        }
      ]
    },
    "destinationConfig": {
      "destinations": [
        {
          "chainName": "EVM",
          "type": "address",
          "identifier": "0xYourSettlementAddress"
        }
      ]
    },
    "memo": {
      "orderId": "order_1234"
    }
  }'
```

The response returns the created `flow`. Store `flow.id`: your payer page needs it for the next steps, and your backend uses `memo` to reconcile the payment later.

<Warning>
  With `disableSwaps: true`, the quote fails with `422` if the payer's source token or chain differs from a configured settlement. List every token and chain you accept in `settlementConfig.settlements`.
</Warning>

To set how long the checkout stays open, pass `expiresIn` (seconds) at creation. 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 call needs no authentication: it mints the flow session token (`dft_...`) that drives the remaining steps.

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={"system"}
    const res = await fetch(
      `https://app.dynamicauth.com/api/v0/sdk/${environmentId}/flow/${flowId}/source`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          sourceType: "deposit_address",
          fromChainName: "EVM",
          fromChainId: "8453",
          refundAddress: "0xPayerRefundAddress",
        }),
      },
    );

    const { sessionToken, flow } = await res.json();
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"system"}
    curl --request POST \
      --url https://app.dynamicauth.com/api/v0/sdk/<YOUR_ENVIRONMENT_ID>/flow/<FLOW_ID>/source \
      --header 'Content-Type: application/json' \
      --data '{
        "sourceType": "deposit_address",
        "fromChainName": "EVM",
        "fromChainId": "8453",
        "refundAddress": "0xPayerRefundAddress"
      }'
    ```
  </Tab>
</Tabs>

* `fromChainName` and `fromChainId` must match a chain in your `settlementConfig.settlements`.
* `refundAddress` is required. If the deposit cannot be delivered, funds return here.
* `fromAddress` is not accepted for a `deposit_address` source: the payer is not connecting a wallet.

Store `sessionToken` immediately. It is returned once, with `Cache-Control: no-store`, and expires at `sessionExpiresAt`.

## Step 3: Get the deposit address

Fetch the quote with the session token. For a deposit address source this call mints the deposit address and returns it on the flow.

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={"system"}
    const res = await fetch(
      `https://app.dynamicauth.com/api/v0/sdk/${environmentId}/flow/${flowId}/quote`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Dynamic-Flow-Session-Token": sessionToken,
        },
        body: JSON.stringify({}),
      },
    );

    const { flow } = await res.json();
    const { depositAddress, quote } = flow;
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"system"}
    curl --request POST \
      --url https://app.dynamicauth.com/api/v0/sdk/<YOUR_ENVIRONMENT_ID>/flow/<FLOW_ID>/quote \
      --header 'Content-Type: application/json' \
      --header 'X-Dynamic-Flow-Session-Token: <SESSION_TOKEN>' \
      --data '{}'
    ```
  </Tab>
</Tabs>

The flow moves to `executionState: "quoted"` and returns:

* `flow.depositAddress`: the address the customer sends funds to
* `flow.quote.fromAmount`: how much of the source token the customer must send
* `flow.quote.expiresAt`: how long the quote is valid

## Step 4: Show the payer the address

Render `depositAddress` and `quote.fromAmount` with the token symbol and chain so the customer knows exactly what to send and where. A QR code of the address works well for mobile wallets.

A deposit address is not reusable across flows: mint a fresh flow for each checkout. If you call quote again on the same flow, the same address comes back rather than a new one.

<Warning>
  There is no prepare, sign, or broadcast step for a deposit address source. The customer pays with any wallet or exchange that can send the settlement token on the settlement chain.
</Warning>

## Step 5: Detect the payment

Flow watches the deposit address and advances the flow on its own. Execution moves to `source_detected` when the transfer arrives and `source_confirmed` once it confirms on chain. Two ways to learn about it: webhooks (recommended) or polling.

### Webhooks

Subscribe your backend to the flow events:

| Event                     | Use it for                                                                       |
| :------------------------ | :------------------------------------------------------------------------------- |
| `flow.execution.updated`  | Detecting the transfer (`newState: "source_detected"` then `"source_confirmed"`) |
| `flow.settlement.updated` | Confirming funds landed (`newState: "completed"` carries `settlementTxHash`)     |

See [Webhooks and events](/docs/flow/webhooks) for the payload shapes and [Webhook setup](/docs/platform/dashboard/webhooks/setup) for endpoint configuration and signature verification.

### Polling

For a prototype or low-volume checkout, poll the read endpoint until the flow reaches a terminal state. It needs no authentication.

```javascript theme={"system"}
const res = await fetch(
  `https://app.dynamicauth.com/api/v0/sdk/${environmentId}/flow/${flowId}`,
);
const { flow } = await res.json();

if (flow.settlementState === "completed") {
  // Paid and settled
}
```

## Step 6: Confirm the payment

Treat `settlementState: "completed"` as paid. Execution reaching `source_confirmed` only means the customer's transfer landed at the deposit address; settlement can still be in progress.

When the settlement completes, reconcile the payment:

* Match `flowId` and the flow's `memo` (for example `orderId` from Step 1) to your internal order.
* Record `settlementTxHash` from the `flow.settlement.updated` payload (or the polled flow) as the on-chain receipt.
* `flow.settlement.completedAt` is the timestamp the funds reached your destination.

If the flow reaches `cancelled`, `expired`, or `failed`, the checkout attempt is over. Create a new flow for another attempt.

## Alternative: send a payment link

If you do not want to build the payer page, create a payment link instead of calling the flow endpoint directly:

```
POST /server/{environmentId}/payment-links
```

The body is the same as Step 1 plus a required `baseUrl` (your hosted payer page origin, which must be a registered CORS origin for the environment). The response returns a `paymentUrl` to send to the customer. See [Payment Links](/docs/flow/payment-links).

## Common pitfalls

* **Do not send `Authorization: Bearer` on `/sdk/` endpoints.** Only the `/server/` creation endpoints accept the API token; SDK endpoints use `X-Dynamic-Flow-Session-Token` or no auth.
* **Quote fails with 422.** The payer's `fromChainName` or `fromChainId` does not match a configured settlement, or a swap would be required while `disableSwaps` is set.
* **`409` on a mutation.** The flow moved past that step. Fetch the flow and resume from its current `executionState` rather than replaying the call.
* **Wrong token or chain sent.** A transfer of a different token to the deposit address is not detected as payment for this flow. The payer's refund path is the `refundAddress` from Step 2.

## Next

<CardGroup cols={3}>
  <Card title="Flow API guide" icon="code" href="/docs/flow/api" />

  <Card title="Webhooks and events" icon="bell" href="/docs/flow/webhooks" />

  <Card title="Settlements" icon="building-columns" href="/docs/flow/settlements" />
</CardGroup>
