> ## 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.

# Fiat Deposit Destinations Quickstart

> Issue a virtual account for a KYC'd customer and simulate an inbound fiat deposit - business virtual account support coming soon

<Warning>
  Fiat deposit destinations are currently in **private beta** and require account enablement. Contact your Coinbase representative for access. Fields and behavior may change before general availability.
</Warning>

This guide walks through the full journey for issuing a fiat deposit destination in Sandbox: onboard a customer, create the account that receives funds, issue the fiat deposit destination, then simulate an inbound deposit and watch it settle as USDC. Every step is a real API call so you can see exactly which endpoints your integration needs to hit.

Fiat deposit destinations are currently created and managed **entirely via API**. They are not accessible in the Portal UI yet.

## Prerequisites

<AccordionGroup>
  <Accordion title="CDP CLI">
    Install the CDP CLI (requires Node.js 22+):

    ```bash theme={null}
    npm install -g @coinbase/cdp-cli
    ```

    Configure a Sandbox environment using your CDP Secret API Key JSON file from the [CDP Portal](https://portal.cdp.coinbase.com). Point it at the Sandbox host so both the `/platform/v2` APIs and the Sandbox-only simulation helper resolve:

    ```bash theme={null}
    cdp env sandbox --key-file ./cdp-api-key.json --url https://sandbox.cdp.coinbase.com
    ```

    <Warning>
      Keep the API key secret. Never commit it to source control.
    </Warning>
  </Accordion>
</AccordionGroup>

<Warning>
  Sandbox does not connect to any bank. Deposit instructions returned here are placeholders for API testing, and deposits are triggered with a simulation helper, not real money movement. Do not send real funds to a Sandbox fiat deposit destination.
</Warning>

## 1. Onboard a KYC'd customer

[Create a customer](/api-reference/v2/rest-api/customers/create-a-customer) with type `individual`, requesting the capabilities this flow needs:

| Capability           | What it enables                                                                                                                     |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `custodyCrypto`      | Hold cryptocurrency in a Coinbase custodial account                                                                                 |
| `custodyFiat`        | Hold fiat currency in a Coinbase custodial account                                                                                  |
| `custodyStablecoin`  | Hold stablecoin in a Coinbase custodial account                                                                                     |
| `transferStablecoin` | Transfer stablecoin to another party (needed in [Step 10](#10-transfer-funds-out-to-a-wallet-address) to send the settled USDC out) |

See the full [capability set](/customers-kyc/capabilities#capability-set) for every capability CDP supports.

In Sandbox, the `fullSsn` value `000-00-0000` is a [magic value](/customers-kyc/requirements#testing-in-sandbox) that forces a deterministic **approval**, so the requested capabilities verify and become `active` without real PII.

```bash theme={null}
CUSTOMER_ID=$(cdp api -X POST /platform/v2/customers -e sandbox --jq=.customerId - <<'JSON'
{
  "type": "individual",
  "individual": {
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com",
    "phoneNumber": "+16175551212",
    "fullSsn": "000-00-0000",
    "dateOfBirth": { "day": "1", "month": "1", "year": "1987" },
    "address": {
      "line1": "500 Main St",
      "city": "Boston",
      "state": "MA",
      "postCode": "02108",
      "countryCode": "US"
    },
    "purposeOfAccount": "investing",
    "sourceOfFunds": "salary"
  },
  "tosAcceptances": [
    { "versionId": "us_individual_2026-05-29", "language": "en", "acceptedAt": "2026-04-17T20:00:00Z" }
  ],
  "taxAttestations": [
    { "form": "us_w9", "isExemptBackupWithholding": true, "edeliveryConsent": true, "acceptedAt": "2026-04-17T20:00:00Z" }
  ],
  "compliance": { "requesterIpAddress": "203.0.113.10" },
  "capabilities": {
    "custodyCrypto": { "requested": true },
    "custodyFiat": { "requested": true },
    "custodyStablecoin": { "requested": true },
    "transferStablecoin": { "requested": true }
  }
}
JSON
)
echo "CUSTOMER_ID=$CUSTOMER_ID"
```

For the full customer lifecycle (identity fields, Terms of Service, tax attestations, and the requirements each capability needs), see the [Customers Quickstart](/customers-kyc/quickstart).

## 2. Confirm the `custodyFiat` capability is active

Creating a fiat deposit destination requires the customer's `custodyFiat` capability to be `active`. Verification is asynchronous; with the approval magic value it settles quickly. Poll with [Get a customer](/api-reference/v2/rest-api/customers/get-a-customer):

```bash theme={null}
cdp api /platform/v2/customers/$CUSTOMER_ID -e sandbox --jq=.capabilities.custodyFiat
```

```json theme={null}
{ "requested": true, "status": "active" }
```

<Note>
  In production, subscribe to the [`customers.capability.changed`](/customers-kyc/capabilities#webhooks) webhook rather than polling. If `custodyFiat` is not `active`, the create call in [Step 4](#4-issue-the-fiat-deposit-destination) returns `403 customer_not_authorized`.
</Note>

## 3. Create the customer's account

[Create account](/api-reference/v2/rest-api/accounts/create-account) with the customer as owner. This is where deposited funds will land as USDC. Passing the customer ID as `owner` makes it customer-owned; customer-facing operations require the end-user's IP on `compliance.requesterIpAddress`.

```bash theme={null}
ACCOUNT_ID=$(cdp api -X POST /platform/v2/accounts -e sandbox \
  owner=$CUSTOMER_ID name="Jane Doe account" \
  compliance.requesterIpAddress=8.8.8.8 \
  --jq=.accountId)
echo "ACCOUNT_ID=$ACCOUNT_ID"
```

## 4. Issue the fiat deposit destination

[Create deposit destination](/api-reference/v2/rest-api/deposit-destinations/create-deposit-destination) with type `fiat` for the account. This is a customer-facing operation, so it also requires the end-user's IP on `compliance.requesterIpAddress`.

Choose a `target` for the incoming funds: credit a CDP account, or route straight out on-chain to an external address as each deposit settles (useful for automated sweeps to a non-custodial address):

<Tabs>
  <Tab title="Credit an account">
    Set `target.asset` to `usdc` so incoming USD is converted and credited as USDC:

    ```bash theme={null}
    DEPOSIT_DESTINATION_ID=$(cdp api -X POST /platform/v2/deposit-destinations -e sandbox --jq=.depositDestinationId \
      accountId=$ACCOUNT_ID \
      type=fiat \
      fiat.currency=usd \
      target.accountId=$ACCOUNT_ID \
      target.asset=usdc \
      compliance.requesterIpAddress=8.8.8.8 \
      'metadata.customer_id=123e4567-e89b-12d3-a456-426614174000')
    echo "DEPOSIT_DESTINATION_ID=$DEPOSIT_DESTINATION_ID"
    ```

    <Accordion title="Example response">
      ```json theme={null}
      {
        "depositDestinationId": "depositDestination_cf4958d2-b068-6bf9-da01-eee44f157336",
        "accountId": "account_af2937b0-9846-4fe7-bfe9-ccc22d935114",
        "type": "fiat",
        "status": "pending",
        "fiat": {
          "accountType": "us_bank",
          "currency": "usd",
          "bankName": "Citibank, N.A.",
          "beneficiaryName": "Jane Doe",
          "routingNumber": "987654321",
          "accountNumber": "123456789",
          "bankAddress": "399 Park Avenue, New York, NY 10022",
          "supportedRails": ["ach"]
        },
        "target": {
          "accountId": "account_af2937b0-9846-4fe7-bfe9-ccc22d935114",
          "asset": "usdc"
        },
        "metadata": {
          "customer_id": "123e4567-e89b-12d3-a456-426614174000"
        },
        "createdAt": "2025-06-01T00:00:00Z",
        "updatedAt": "2025-06-01T00:00:00Z"
      }
      ```
    </Accordion>
  </Tab>

  <Tab title="Onchain address">
    Set `target.address`, `target.network`, and `target.asset` instead of `target.accountId`:

    ```bash theme={null}
    DEPOSIT_DESTINATION_ID=$(cdp api -X POST /platform/v2/deposit-destinations -e sandbox --jq=.depositDestinationId \
      accountId=$ACCOUNT_ID \
      type=fiat \
      fiat.currency=usd \
      target.address=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
      target.network=base \
      target.asset=usdc \
      compliance.requesterIpAddress=8.8.8.8 \
      'metadata.customer_id=123e4567-e89b-12d3-a456-426614174000')
    echo "DEPOSIT_DESTINATION_ID=$DEPOSIT_DESTINATION_ID"
    ```

    <Accordion title="Example response">
      ```json theme={null}
      {
        "depositDestinationId": "depositDestination_cf4958d2-b068-6bf9-da01-eee44f157336",
        "accountId": "account_af2937b0-9846-4fe7-bfe9-ccc22d935114",
        "type": "fiat",
        "status": "pending",
        "fiat": {
          "accountType": "us_bank",
          "currency": "usd",
          "bankName": "Citibank, N.A.",
          "beneficiaryName": "Jane Doe",
          "routingNumber": "987654321",
          "accountNumber": "123456789",
          "bankAddress": "399 Park Avenue, New York, NY 10022",
          "supportedRails": ["ach"]
        },
        "target": {
          "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
          "network": "base",
          "asset": "usdc"
        },
        "metadata": {
          "customer_id": "123e4567-e89b-12d3-a456-426614174000"
        },
        "createdAt": "2025-06-01T00:00:00Z",
        "updatedAt": "2025-06-01T00:00:00Z"
      }
      ```
    </Accordion>
  </Tab>
</Tabs>

The rest of this guide continues with a deposit destination that credits an account. If you used the onchain address target instead, deposits forward automatically once the destination is `active` - there's nothing to transfer out manually in [Step 10](#10-transfer-funds-out-to-a-wallet-address).

<Note>
  If `custodyFiat` is not active, this returns `403` with `errorType: customer_not_authorized` and the missing capability under `unauthorizedCapabilities`. Resolve it ([Step 2](#2-confirm-the-custodyfiat-capability-is-active)) and retry.
</Note>

The response `status` is `pending`: the banking partner is still provisioning the account.

## 5. Wait for activation

Poll [Get deposit destination](/api-reference/v2/rest-api/deposit-destinations/get-deposit-destination) until the fiat deposit destination is `active`. Only then can it receive deposits.

```bash theme={null}
cdp api /platform/v2/deposit-destinations/$DEPOSIT_DESTINATION_ID -e sandbox --jq=.status
```

```json theme={null}
"active"
```

<Warning>
  **No webhook fires** for fiat deposit destination creation or the `pending → active` transition. You must poll `GET /v2/deposit-destinations/{depositDestinationId}` to confirm `status` is `active` before sharing deposit instructions or simulating a deposit.
</Warning>

## 6. Share the deposit instructions

Once active, the `fiat` object holds the bank coordinates the depositor uses to send USD:

| Field             | Purpose                                                                                                   |
| ----------------- | --------------------------------------------------------------------------------------------------------- |
| `bankName`        | The receiving bank (e.g. `Citibank, N.A.`)                                                                |
| `beneficiaryName` | The account holder name funds are sent to                                                                 |
| `routingNumber`   | ABA routing number, used for ACH                                                                          |
| `accountNumber`   | The bank account number                                                                                   |
| `bankAddress`     | Bank address, present when required by the receiving institution                                          |
| `referenceCode`   | When present, must be included in the payment memo/reference so the deposit routes to the correct account |
| `supportedRails`  | The rails this account can receive on (e.g. `["ach"]`)                                                    |

ACH is currently the only publicly supported rail.

<Note>
  A fiat deposit destination can be funded by the customer themselves (the sender name must match the KYC'd customer).
</Note>

## 7. Simulate a deposit

Because Sandbox is not connected to a bank, use the simulation endpoint to trigger an inbound deposit. It works for both crypto and fiat destinations (the type is derived from the destination), so no rail details are needed. For a fiat destination, `asset` defaults to the destination's currency (`usd`).

```bash theme={null}
cdp api -X POST /platform/v2/simulations/deposits -e sandbox \
  depositDestinationId=$DEPOSIT_DESTINATION_ID \
  amount=100.00 \
  asset=usd
```

<Accordion title="Example response">
  ```json theme={null}
  {
    "transferId": "transfer_b340437d-4705-446f-8852-2345c83ace60"
  }
  ```
</Accordion>

After simulation:

* **Webhook events fire**: `payments.transfers.processing` then `payments.transfers.completed`
* **Transfer record is created**: visible via [List transfers](/api-reference/v2/rest-api/transfers/list-transfers)
* **Balance is credited**: the account receives USDC (converted 1:1 from USD)

## 8. Inspect the webhook payload

When the deposit settles, CDP fires a `payments.transfers.completed` webhook to your subscribed endpoint. The payload includes the transfer record, a reference back to the fiat deposit destination, and any metadata set on it.

<Note>
  This requires a webhook subscription; CDP has nowhere to deliver the event otherwise. See [Create webhook subscription](/api-reference/v2/rest-api/webhooks/create-webhook-subscription) and [Transfer Webhooks](/webhooks/transfers/overview) to set one up.
</Note>

<Accordion title="Example webhook payload">
  ```json theme={null}
  {
    "eventID": "4557efb9-391b-4a9d-987d-d263b9d7fd37",
    "eventType": "payments.transfers.completed",
    "timestamp": "2026-01-21T20:15:04Z",
    "data": {
      "transferId": "transfer_af2937b0-9846-4fe7-bfe9-ccc22d935114",
      "status": "completed",
      "createdAt": "2026-01-21T20:12:46Z",
      "completedAt": "2026-01-21T20:15:04Z",
      "source": {
        "currency": "usd",
        "companyName": "A*** C***",
        "companyEntryDescription": "PAYROLL",
        "individualIdentificationNumber": "J*** D***"
      },
      "sourceAmount": "100.00",
      "sourceAsset": "usd",
      "target": {
        "accountId": "account_af2937b0-9846-4fe7-bfe9-ccc22d935114",
        "asset": "usdc"
      },
      "targetAmount": "100.00",
      "targetAsset": "usdc",
      "details": {
        "depositDestination": {
          "id": "depositDestination_cf4958d2-b068-6bf9-da01-eee44f157336"
        }
      },
      "metadata": {
        "customer_id": "123e4567-e89b-12d3-a456-426614174000"
      }
    }
  }
  ```
</Accordion>

A `payments.transfers.processing` event fires earlier when the deposit is first detected. For most integrations, listening for `completed` is sufficient. If a deposit is returned, a `payments.transfers.failed` event fires with the reason.

## 9. Verify the balance update

Confirm the deposit credited to the account as USDC with [List balances for account](/api-reference/v2/rest-api/accounts/list-balances-for-account):

```bash theme={null}
cdp api /platform/v2/accounts/$ACCOUNT_ID/balances -e sandbox --jq=.balances
```

## 10. Transfer funds out to a wallet address

Once the deposit settles as USDC, use [Create transfer](/api-reference/v2/rest-api/transfers/create-transfer) to send it out to an external wallet address (`target.address`), or back to a bank rail via a payment method (`target.paymentMethodId`). This is also a customer-facing operation, so it requires both the `transferStablecoin` capability from [Step 1](#1-onboard-a-kycd-customer) and the end-user's IP on `compliance.requesterIpAddress`:

<Note>
  This is a manual, per-transfer call. For automated sweeps to a non-custodial address, no manual transfer needed, set the deposit destination's `target` to an onchain address at creation time ([Step 4](#4-issue-the-fiat-deposit-destination)) instead of an account. Deposits are then routed to that address automatically as they settle.
</Note>

<Tabs>
  <Tab title="CDP CLI">
    ```bash theme={null}
    cdp api -X POST /platform/v2/transfers -e sandbox \
      source.accountId=$ACCOUNT_ID \
      source.asset=usdc \
      target.network=base \
      target.address=0x1111111111111111111111111111111111111111 \
      target.asset=usdc \
      amount=100.00 \
      asset=usdc \
      compliance.requesterIpAddress=8.8.8.8 \
      'execute:=true'
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript main.ts theme={null}
    import { randomUUID } from "node:crypto";

    const transfer = await cdp.transfers.createTransfer({
      idempotencyKey: randomUUID(),
      source: { accountId: "YOUR_ACCOUNT_ID", asset: "usdc" },
      target: {
        address: "YOUR_WALLET_ADDRESS",
        network: "base",
        asset: "usdc",
      },
      amount: "100.00",
      asset: "usdc",
      compliance: { requesterIpAddress: "8.8.8.8" },
      execute: true,
    });

    console.log(`${transfer.transferId}: ${transfer.status}`);
    ```
  </Tab>
</Tabs>

In Sandbox, `0x1111111111111111111111111111111111111111` is the reserved success address. See the [Transfers Quickstart](/payments/transfers/quickstart) for the full set of reserved addresses and failure cases (including the [payment method withdrawal example](/payments/transfers/quickstart#3-create-a-fiat-withdrawal-to-a-payment-method)), and the [Transfers overview](/payments/transfers/overview) for fee quotes and travel rule requirements.

## Move to production

To run this flow for real, switch the Sandbox base URL to the production base URL and use a production API key. In production there are no magic values (customers complete real KYC), and fiat deposit destinations are provisioned at a real banking partner. Fiat deposit destinations are currently API-only; they are not accessible in the Portal UI yet.

## What to read next

<CardGroup cols={2}>
  <Card title="Deposit Destinations overview" icon="circle-info" href="/payments/deposit-destinations/overview">
    Crypto and fiat deposit destinations: concepts, rails, lifecycle, and who can send funds
  </Card>

  <Card title="Customers & KYC" icon="user-check" href="/customers-kyc/quickstart">
    Onboard and verify the customer that owns the fiat deposit destination
  </Card>

  <Card title="Transfers" icon="arrow-right-arrow-left" href="/payments/transfers/quickstart">
    Move funds out of an account to a wallet address, email, or payment method
  </Card>

  <Card title="Webhooks" icon="webhook" href="/webhooks/transfers/overview">
    Subscribe to real-time deposit event notifications
  </Card>

  <Card title="REST API reference" icon="code" href="/api-reference/v2/rest-api/deposit-destinations/deposit-destinations">
    Full Deposit Destinations API reference
  </Card>
</CardGroup>
