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

# Pay out USDC from a USD balance

> Fund a USD treasury and pay out USDC onchain to any wallet, with the CDP CLI or SDK.

<Note>
  Custodial Wallets require a business account. This recipe assumes your platform is already onboarded with Custodial Wallets enabled. If you're interested or want to check whether this fits your use case, [get in touch](https://www.coinbase.com/developer-platform/developer-interest) and our team will follow up.
</Note>

**Use case:** Your business holds USD, but you need to pay recipients (workers, sellers, partners) in stablecoins, sent straight to their wallets onchain.

**What you'll build:** Fund a USD treasury, then pay out USDC onchain to any wallet address. Coinbase handles the USD-to-USDC conversion and the onchain send for you (a "delegated" payout), so you never hold stablecoins yourself.

These steps run in [Sandbox](/get-started/sandbox/quickstart), Coinbase's test environment. To go live, point at production (`https://api.cdp.coinbase.com/platform` for the SDK, or `cdp env production` for the CLI) and use a production API key.

## Flow of funds

<Frame>
  <img src="https://mintcdn.com/coinbase-prod/ZbqWonTH7Vh1UwhR/images/recipes/pay-out-usdc-flow.png?fit=max&auto=format&n=ZbqWonTH7Vh1UwhR&q=85&s=aec7bcbc273cc81d470a04805377d0c6" alt="Flow of funds: a USD treasury account funds a delegated payout, where Coinbase converts USD to USDC and sends it onchain to the beneficiary's wallet." width="1999" height="487" data-path="images/recipes/pay-out-usdc-flow.png" />
</Frame>

## Prerequisites

* Your entity is onboarded (KYB'd) with **Custodial Wallets enabled**. See the [Custodial Wallets overview](/wallets/custodial-wallets/overview).
* For delegated payouts, your **originator terms of service** must be accepted and passed on each payout (shown in Step 3). This is what authorizes Coinbase to convert and send on your behalf.
* The CDP CLI or TypeScript SDK installed and pointed at Sandbox. Both require [Node.js](https://nodejs.org/) 22 or later.

## Account structure

| Role                          | Account                              | Purpose                                                               |
| ----------------------------- | ------------------------------------ | --------------------------------------------------------------------- |
| Your platform (entity, KYB'd) | Treasury account (USD, entity-owned) | Holds USD and funds payouts                                           |
| Your recipient (beneficiary)  | External wallet (no CDP account)     | Receives USDC onchain; their details are passed in the payout request |

## Install and configure

<Tabs>
  <Tab title="CDP CLI">
    ```bash theme={null}
    npm install -g @coinbase/cdp-cli
    cdp env sandbox --key-file ~/Downloads/cdp_api_key.json
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```bash theme={null}
    npm install @coinbase/cdp-sdk
    export CDP_API_KEY_ID="YOUR_API_KEY_ID"
    export CDP_API_KEY_SECRET="YOUR_API_KEY_SECRET"
    ```

    ```typescript main.ts theme={null}
    import { CdpClient } from "@coinbase/cdp-sdk";
    import { randomUUID } from "node:crypto";

    const cdp = new CdpClient({
      basePath: "https://sandbox.cdp.coinbase.com/platform",
    });
    ```
  </Tab>
</Tabs>

<Note>
  Pick one path, the **CDP CLI** or the **TypeScript SDK**, and follow that tab in every step. Each CLI command runs on its own. The SDK snippets build up a single `main.ts` file, so the complete runnable script is at the end under **Run the SDK flow as scripts**.
</Note>

<Note>
  **Copying these commands:** anything shown as a placeholder — values ending in `...` (like `account_...`) or wrapped in `<ANGLE_BRACKETS>` — must be replaced with your real value before running. Use each code block's copy button; if you paste through a rich-text editor (Google Docs, Notes), straight quotes `"` can turn into curly `"` and the shell will hang on a `dquote>` prompt.
</Note>

## 1. Create your treasury account

Your entity-owned USD account. Omitting an owner makes the account entity-owned.

<Tabs>
  <Tab title="CDP CLI">
    ```bash theme={null}
    TREASURY_ACCOUNT_ID=$(cdp accounts create name="Treasury account" -e sandbox --jq=.accountId)
    echo "TREASURY_ACCOUNT_ID=$TREASURY_ACCOUNT_ID"
    ```

    Confirm it is entity-owned. The owner should start with `entity_`:

    ```bash theme={null}
    cdp accounts get $TREASURY_ACCOUNT_ID -e sandbox --jq=.owner
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript main.ts theme={null}
    const treasury = await cdp.accounts.createAccount({
      idempotencyKey: randomUUID(),
      name: "Treasury account",
    });
    ```
  </Tab>
</Tabs>

## 2. Fund the treasury with USD

**In Sandbox**, there are no real wires, so add a USD test balance in the Portal: Accounts → your treasury account → **Edit test assets** → set USD, then **Save changes**.

<Frame caption="Edit test assets: set the treasury's USD balance (here, 100).">
  <img src="https://mintcdn.com/coinbase-prod/ZbqWonTH7Vh1UwhR/images/recipes/pay-out-usdc-add-test-assets.png?fit=max&auto=format&n=ZbqWonTH7Vh1UwhR&q=85&s=7bbf775bba25ba8337879a06554cac30" alt="The Edit test assets modal in the CDP Portal, with the USD amount set to 100." width="340" data-path="images/recipes/pay-out-usdc-add-test-assets.png" />
</Frame>

<Frame caption="The treasury account funded with $100 USD (entity-owned).">
  <img src="https://mintcdn.com/coinbase-prod/ZbqWonTH7Vh1UwhR/images/recipes/pay-out-usdc-treasury-funded.png?fit=max&auto=format&n=ZbqWonTH7Vh1UwhR&q=85&s=e56eb6d5167e3a0664ada781023f5c87" alt="The treasury account overview in the CDP Portal showing a $100 USD balance." width="945" height="590" data-path="images/recipes/pay-out-usdc-treasury-funded.png" />
</Frame>

**In production**, you fund by wire: provision a **fiat deposit destination** to get a virtual bank account, then wire USD to it. This is a gated feature enabled per entity, so [get in touch](https://www.coinbase.com/developer-platform/developer-interest) to turn it on. See [Fiat deposit destinations](/payments/fiat-deposit-destinations/quickstart) for the setup.

## 3. Pay out USDC to a beneficiary's wallet

This is a **delegated payout**: the source is your USD balance, the target is an onchain address receiving `usdc`, and `fulfillmentMode: "delegated"` tells Coinbase to convert the USD to USDC and send it onchain for you. Include `compliance` with your originator terms-of-service acceptance and the beneficiary's details.

<Note>
  In Sandbox there's no real recipient wallet, so use a reserved test address like `0x1111111111111111111111111111111111111111` (it settles as a success). In production, use the beneficiary's real wallet address. The `compliance.originator` `versionId` is your entity's current terms-of-service version, read from its `requirements`; Sandbox doesn't validate it, so unlike other placeholders in this guide, you can leave `<REQUIRED_TOS_VERSION_ID>` as-is (or use any non-empty string) when testing.
</Note>

<Warning>
  The `fulfillmentMode` and `compliance` fields are honored by the API but are not yet in the published `@coinbase/cdp-sdk` types, so strict TypeScript rejects them (`TS2353`). Until the SDK types are updated, cast the request with `as any` (shown in the SDK tab). This does not affect the CLI.
</Warning>

<Tabs>
  <Tab title="CDP CLI">
    ```bash theme={null}
    # Sandbox: this reserved test address settles as a success. In production, use the beneficiary's real wallet.
    export BENEFICIARY_ADDRESS="0x1111111111111111111111111111111111111111"

    TRANSFER_ID=$(cdp transfers create -e sandbox --jq=.transferId - <<JSON
    {
      "source": { "accountId": "$TREASURY_ACCOUNT_ID", "asset": "usd" },
      "target": { "address": "$BENEFICIARY_ADDRESS", "network": "base", "asset": "usdc" },
      "fulfillmentMode": "delegated",
      "amount": "50.00",
      "asset": "usd",
      "execute": true,
      "metadata": { "reference": "payout-0001" },
      "compliance": {
        "originator": {
          "name": "Your Company, Inc.",
          "tosAcceptances": [
            { "versionId": "<REQUIRED_TOS_VERSION_ID>", "language": "en", "acceptedAt": "2026-08-18T22:00:00Z" }
          ]
        },
        "beneficiary": {
          "name": "Jane Smith",
          "address": { "line1": "456 Oak Ave", "city": "Austin", "state": "TX", "postCode": "78701", "countryCode": "US" }
        }
      }
    }
    JSON
    )
    echo "TRANSFER_ID=$TRANSFER_ID"
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript main.ts theme={null}
    const payout = await cdp.transfers.createTransfer({
      idempotencyKey: randomUUID(),
      source: { accountId: treasury.accountId, asset: "usd" },
      // Sandbox: reserved test address. In production, use the beneficiary's real wallet.
      target: { address: "0x1111111111111111111111111111111111111111", network: "base", asset: "usdc" },
      fulfillmentMode: "delegated",
      amount: "50.00",
      asset: "usd",
      execute: true,
      metadata: { reference: "payout-0001" },
      compliance: {
        originator: {
          name: "Your Company, Inc.",
          tosAcceptances: [
            { versionId: "<REQUIRED_TOS_VERSION_ID>", language: "en", acceptedAt: "2026-08-18T22:00:00Z" },
          ],
        },
        beneficiary: {
          name: "Jane Smith",
          address: { line1: "456 Oak Ave", city: "Austin", state: "TX", postCode: "78701", countryCode: "US" },
        },
      },
    } as any); // fulfillmentMode + compliance work at runtime but aren't in the SDK types yet
    console.log("Payout:", payout.status);
    ```
  </Tab>
</Tabs>

<Info>
  `amount` and `asset` are in `usd` (what you spend); the target `asset` is `usdc` (what the beneficiary receives). Coinbase handles the conversion. The CLI passes the JSON body to `cdp transfers create` on stdin (`-`), so the nested `compliance` block is sent exactly as written.
</Info>

## 4. Confirm the payout

The payout settles asynchronously; you get a `payments.transfers.completed` webhook when the USDC lands onchain. You can also poll it:

<Tabs>
  <Tab title="CDP CLI">
    ```bash theme={null}
    cdp transfers get $TRANSFER_ID -e sandbox --jq=.status
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript main.ts theme={null}
    const got = await cdp.transfers.getTransferById({ transferId: payout.transferId! });
    console.log("Confirmed:", got.status);
    ```
  </Tab>
</Tabs>

<Note>
  In Sandbox the payout settles immediately, so this check returns `completed`. In production the first check usually shows `quoted` or `processing`; rely on the `payments.transfers.completed` webhook, or poll until the status reaches `completed`.
</Note>

<Frame caption="After the payout, the treasury's USD balance drops from \$100 to \$50.">
  <img src="https://mintcdn.com/coinbase-prod/ZbqWonTH7Vh1UwhR/images/recipes/pay-out-usdc-treasury-after.png?fit=max&auto=format&n=ZbqWonTH7Vh1UwhR&q=85&s=87df1f899864f2f925798a40dec0d2f9" alt="The treasury account overview in the CDP Portal showing a $50 USD balance after a $50 payout." width="945" height="590" data-path="images/recipes/pay-out-usdc-treasury-after.png" />
</Frame>

<Accordion title="Run the SDK flow as scripts">
  The SDK snippets above build on one client and belong in a single file. Because funding the treasury (Step 2) happens in the Portal, run it as two scripts: create the account, fund it, then pay out. Save each as a `.ts` file and run it with `npx tsx <file>.ts` (Node 22 or later; `tsx` runs TypeScript directly, no build step).

  **Part 1 — create the treasury (Step 1).** Prints the account ID to fund.

  ```typescript main.ts theme={null}
  import { CdpClient } from "@coinbase/cdp-sdk";
  import { randomUUID } from "node:crypto";

  const cdp = new CdpClient({ basePath: "https://sandbox.cdp.coinbase.com/platform" });

  async function main() {
    const treasury = await cdp.accounts.createAccount({
      idempotencyKey: randomUUID(),
      name: "Treasury account",
    });
    console.log("TREASURY_ACCOUNT_ID =", treasury.accountId);
  }

  main().catch(console.error);
  ```

  Fund that account in the Portal (Step 2), then run Part 2.

  **Part 2 — pay out and confirm (Steps 3 to 4).** Paste the treasury account ID Part 1 printed.

  ```typescript main.ts theme={null}
  import { CdpClient } from "@coinbase/cdp-sdk";
  import { randomUUID } from "node:crypto";

  const cdp = new CdpClient({ basePath: "https://sandbox.cdp.coinbase.com/platform" });

  const TREASURY_ACCOUNT_ID = "account_..."; // from Part 1, after funding it in the Portal

  async function main() {
    const payout = await cdp.transfers.createTransfer({
      idempotencyKey: randomUUID(),
      source: { accountId: TREASURY_ACCOUNT_ID, asset: "usd" },
      // Sandbox: reserved test address. In production, use the beneficiary's real wallet.
      target: { address: "0x1111111111111111111111111111111111111111", network: "base", asset: "usdc" },
      fulfillmentMode: "delegated",
      amount: "50.00",
      asset: "usd",
      execute: true,
      metadata: { reference: "payout-0001" },
      compliance: {
        originator: {
          name: "Your Company, Inc.",
          tosAcceptances: [
            { versionId: "<REQUIRED_TOS_VERSION_ID>", language: "en", acceptedAt: "2026-08-18T22:00:00Z" },
          ],
        },
        beneficiary: {
          name: "Jane Smith",
          address: { line1: "456 Oak Ave", city: "Austin", state: "TX", postCode: "78701", countryCode: "US" },
        },
      },
    } as any); // fulfillmentMode + compliance work at runtime but aren't in the SDK types yet
    console.log("Payout:", payout.status);

    const got = await cdp.transfers.getTransferById({ transferId: payout.transferId! });
    console.log("Confirmed:", got.status);
  }

  main().catch(console.error);
  ```
</Accordion>

## Good to know

* **Delegated payout:** with `fulfillmentMode: "delegated"`, Coinbase debits your USD, converts it to USDC, and sends it onchain. You fund only in USD and never custody stablecoins.
* **Originator terms of service** must be accepted and passed in the `compliance.originator` block. That acceptance is what authorizes Coinbase to run the delegated payout. Fetch the required version from the entity `requirements`.
* **Reconciliation:** attach `metadata` (a reference or customer ID) to each payout, and query history with `cdp transfers list` or the Transfers API.
* **Idempotency:** every SDK call passes an `idempotencyKey`, so a retried request returns the same result instead of creating a duplicate payout. The `cdp transfers create` CLI command has no idempotency option, so for production prefer the SDK (or the REST API, which accepts an idempotency key) for safe retries.

## What to read next

<CardGroup cols={2}>
  <Card title="Customers / KYC" icon="user-check" href="/customers-kyc/quickstart">
    Onboard the entity and customers behind these payouts
  </Card>

  <Card title="Transfers" icon="arrow-right-arrow-left" href="/payments/transfers/quickstart">
    All transfer types, rails, and lifecycle
  </Card>

  <Card title="Deposit Destinations" icon="arrow-down-to-line" href="/payments/deposit-destinations/overview">
    Fiat and crypto deposit destinations
  </Card>

  <Card title="Webhooks" icon="webhook" href="/webhooks/transfers/overview">
    React to payout status in production
  </Card>
</CardGroup>
