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

# Send from a wallet

> Use the quote and transaction flow to move USDC out of a custodial wallet. Crypto destinations work today; bank payouts are early access.

<Note>
  **Before you start:** the OMS API is in early access. Every endpoint, including the ones in this guide, requires an early-access API key. [Request access](https://info.polygon.technology/get-early-access?utm_source=docs\&utm_medium=card\&utm_campaign=oms_access) before you begin.

  Authenticate by exchanging your API key and secret for a bearer token at `POST /auth/token`, then send it as `Authorization: Bearer {token}` on every request. See [Get started](/payments/get-started) for the full flow.
</Note>

This guide shows you how to move USDC out of an OMS custodial wallet using the two-step quote and transaction flow. The crypto-to-crypto path described first is fully supported today. Paying out to a bank account is covered at the end and is early access.

<div style={{border:"1px solid #C8CFE1",borderRadius:"12px",overflow:"hidden",marginBottom:"24px"}}>
  <div style={{background:"linear-gradient(180deg,#EAE4F5 0%,#F6F3FB 100%)",borderBottom:"1px solid #D5C4F2",padding:"10px 16px",fontSize:"11px",fontWeight:"700",color:"#670DE5",letterSpacing:"0.06em",textTransform:"uppercase"}}>Send from a wallet flow</div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>1</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{fontFamily:"'Geist Mono',ui-monospace,monospace",fontSize:"12px",color:"#141635"}}>POST /quotes (USDC/polygon → USDC/polygon)</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>2</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{color:"#48526F",fontWeight:"700"}}>→</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{fontSize:"13px",color:"#141635"}}>Quote with locked pricing</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>3</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{fontFamily:"'Geist Mono',ui-monospace,monospace",fontSize:"12px",color:"#141635"}}>POST /transactions \{quoteId}</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>4</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>Polygon</span>
    <span style={{fontSize:"13px",color:"#141635"}}>Pull USDC and send onchain</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>5</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{fontFamily:"'Geist Mono',ui-monospace,monospace",fontSize:"12px",color:"#141635"}}>Webhook: cryptoToCrypto.processing</span>
  </div>

  <div style={{padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>6</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{fontFamily:"'Geist Mono',ui-monospace,monospace",fontSize:"12px",color:"#141635"}}>Webhook: cryptoToCrypto.completed</span>
  </div>
</div>

## Prerequisites

Before sending USDC from a wallet, you need:

1. **A customer** with a `cst_` ID.
2. **A funded OMS wallet** with a `wlt_` ID containing enough USDC to cover the source amount plus gas (if not sponsoring gas).
3. **A destination**: another OMS wallet (its `wlt_` ID) or an external onchain address (a `0x...` address on the same network).
4. **Webhook subscriptions** configured in the OMS Dashboard for `transaction.cryptoToCrypto.*` events.

## Step 1: Create a quote

Create a quote to lock in the pricing. OMS infers the transaction type as `cryptoToCrypto` from the source and destination asset pair (USDC to USDC).

The quote `source` must be an OMS crypto wallet: a `walletId`, `asset`, and `network`. The `destination` describes where the funds land. To send to an external onchain address, set `destination.wallet.blockchainAddress`. To send to another OMS wallet, set `destination.wallet.id` instead.

### Request

```
POST /v0.9/quotes
Authorization: Bearer {token}
Idempotency-Key: qt-alice-usdc-send-001
Content-Type: application/json
```

```json theme={null}
{
  "customerId": "cst_01H9Xa...",
  "source": {
    "walletId": "wlt_01H9Xb...",
    "asset": "usdc",
    "network": "polygon",
    "amount": "2000.00"
  },
  "destination": {
    "wallet": {
      "blockchainAddress": "0xAbC1230000000000000000000000000000DeF456"
    },
    "asset": "usdc",
    "network": "polygon"
  },
  "sponsorGas": true
}
```

**Required fields:**

* `customerId`: The customer making this transaction.
* `source.walletId`: The OMS custodial wallet holding the USDC to send (string, `wlt_` prefix).
* `source.asset`: The crypto asset to send (`usdc` or `usdt`).
* `source.network`: The blockchain the wallet is on (`polygon` or `ethereum`).
* `source.amount` or `destination.amount`: Exactly one is required. Here we specify the source amount (send 2,000 USDC). OMS calculates the destination amount after fees. You could alternatively specify `destination.amount` to target an exact received amount.
* `destination.wallet`: Where the funds land. Provide one of `id` (another OMS wallet), `blockchainAddress` (an external onchain address), or `externalAccount` (a saved bank account, early access). This example uses `blockchainAddress`.
* `destination.asset`: `usdc`.
* `destination.network`: The network the funds are delivered on. For a crypto-to-crypto send this matches the source network (`polygon`).

**Optional fields:**

* `sponsorGas`: When `true`, the developer absorbs gas fees. The customer sees fees on the source side as zero and the actual gas cost appears in `sponsorGasCost`.
* `developerFees`: Array of fee entries. Omitted here, so no developer fee is charged.
* `metadata`: Up to 20 arbitrary key-value pairs.

### Response, `201 Created`

```json theme={null}
{
  "id": "qt_01H9Xt...",
  "object": "quote",
  "type": "cryptoToCrypto",
  "status": "open",
  "customerId": "cst_01H9Xa...",
  "source": {
    "walletId": "wlt_01H9Xb...",
    "asset": "usdc",
    "network": "polygon",
    "amountGross": "2000.00",
    "amountNet": "2000.00",
    "feesDeducted": {
      "total": "0.00",
      "developer": "0.00",
      "oms": "0.00",
      "gas": "0.00"
    }
  },
  "destination": {
    "wallet": {
      "blockchainAddress": "0xAbC1230000000000000000000000000000DeF456"
    },
    "asset": "usdc",
    "network": "polygon",
    "amountGross": "1998.50",
    "amountNet": "1998.50",
    "feesDeducted": {
      "total": "1.50",
      "developer": "0.00",
      "oms": "1.50",
      "gas": "0.00"
    }
  },
  "fixedAmountSide": "source",
  "sponsorGasCost": "0.38",
  "sponsorGas": true,
  "developerFees": [],
  "rates": {
    "pair": "usdc/usdc",
    "exchangeRate": "1.0000",
    "effectiveRate": "0.9993"
  },
  "metadata": null,
  "expiresAt": "2024-01-15T10:05:00Z",
  "createdAt": "2024-01-15T10:00:00Z"
}
```

**Key fields in the response:**

* `id`: The quote ID (prefix `qt_`). You'll use this to create the transaction.
* `status: "open"`: Pricing is locked, awaiting acceptance.
* `type: "cryptoToCrypto"`: Inferred from a USDC source and USDC destination.
* `destination.amountNet: "1998.50"`: What the destination address receives. Calculated as: 2,000.00 USDC − $1.50 OMS fee − $0.00 developer fee = 1,998.50 USDC.
* `sponsorGasCost: "0.38"`: The estimated gas cost the developer will absorb. This is an out-of-band cost, not deducted from the destination amount.
* `destination.feesDeducted.gas: "0.00"`: Shows as zero to the customer because gas is sponsored.
* `expiresAt`: Trails-routed quotes have a \~5-minute TTL.

If the customer accepts the pricing, proceed to Step 2. If the quote expires, create a new one.

## Step 2: Create the transaction

Accept the quote by creating a transaction that references the `quoteId`. This is the point of no return: USDC is pulled from the wallet and the onchain send begins.

### Request

```
POST /v0.9/transactions
Authorization: Bearer {token}
Idempotency-Key: txn-alice-usdc-send-001
Content-Type: application/json
```

```json theme={null}
{
  "quoteId": "qt_01H9Xt..."
}
```

The request body is intentionally thin: the quote is the contract. All source, destination, and fee details were locked in Step 1. The transaction ID prefix is `txn_`.

### Response, `201 Created`

```json theme={null}
{
  "id": "txn_01H9Xd...",
  "object": "transaction",
  "type": "cryptoToCrypto",
  "status": "processing",
  "subStatus": "processing.fundsPulled",
  "customerId": "cst_01H9Xa...",
  "quoteId": "qt_01H9Xt...",
  "depositAddress": null,
  "virtualAccount": null,
  "cashIn": null,
  "source": {
    "walletId": "wlt_01H9Xb...",
    "asset": "usdc",
    "network": "polygon",
    "amountGross": "2000.00",
    "amountNet": "2000.00",
    "feesDeducted": {
      "total": "0.00",
      "developer": "0.00",
      "oms": "0.00",
      "gas": "0.00"
    },
    "txHash": "0x7f2a9b...c3d4e5"
  },
  "destination": {
    "wallet": {
      "blockchainAddress": "0xAbC1230000000000000000000000000000DeF456"
    },
    "asset": "usdc",
    "network": "polygon",
    "amountGross": "1998.50",
    "amountNet": "1998.50",
    "feesDeducted": {
      "total": "1.50",
      "developer": "0.00",
      "oms": "1.50",
      "gas": "0.00"
    }
  },
  "fixedAmountSide": "source",
  "sponsorGasCost": "0.42",
  "sponsorGas": true,
  "rates": {
    "pair": "usdc/usdc",
    "exchangeRate": "1.0000",
    "effectiveRate": "0.9993"
  },
  "error": null,
  "metadata": null,
  "createdAt": "2024-01-15T10:01:00Z",
  "updatedAt": "2024-01-15T10:01:00Z"
}
```

**What to notice:**

* `status: "processing"`: USDC has been pulled from the wallet and the onchain send is underway.
* `subStatus: "processing.fundsPulled"`: Granular sub-status using dot notation, indicating the USDC has been pulled. The next sub-status reflects send progress.
* `source.txHash: "0x7f2a9b...c3d4e5"`: The onchain transaction hash for the send.
* `sponsorGasCost: "0.42"`: Final gas cost (may differ slightly from the quote estimate of \$0.38).

### Webhook, `transaction.cryptoToCrypto.processing`

OMS fires this webhook when the transaction enters processing:

```json theme={null}
{
  "id": "whk_evt_01H9Xw...",
  "event": "transaction.cryptoToCrypto.processing",
  "createdAt": "2024-01-15T10:01:00Z",
  "data": {
    "id": "txn_01H9Xd...",
    "object": "transaction",
    "type": "cryptoToCrypto",
    "status": "processing",
    "subStatus": "processing.fundsPulled",
    "customerId": "cst_01H9Xa...",
    "quoteId": "qt_01H9Xt...",
    "depositAddress": null,
    "virtualAccount": null,
    "cashIn": null,
    "source": {
      "walletId": "wlt_01H9Xb...",
      "asset": "usdc",
      "network": "polygon",
      "amountGross": "2000.00",
      "amountNet": "2000.00",
      "feesDeducted": {
        "total": "0.00",
        "developer": "0.00",
        "oms": "0.00",
        "gas": "0.00"
      },
      "txHash": "0x7f2a9b...c3d4e5"
    },
    "destination": {
      "wallet": {
        "blockchainAddress": "0xAbC1230000000000000000000000000000DeF456"
      },
      "asset": "usdc",
      "network": "polygon",
      "amountGross": "1998.50",
      "amountNet": "1998.50",
      "feesDeducted": {
        "total": "1.50",
        "developer": "0.00",
        "oms": "1.50",
        "gas": "0.00"
      }
    },
    "fixedAmountSide": "source",
    "sponsorGasCost": "0.42",
    "sponsorGas": true,
    "rates": {
      "pair": "usdc/usdc",
      "exchangeRate": "1.0000",
      "effectiveRate": "0.9993"
    },
    "error": null,
    "metadata": null,
    "createdAt": "2024-01-15T10:01:00Z",
    "updatedAt": "2024-01-15T10:01:00Z"
  }
}
```

## Step 3: Track the transaction

OMS pulls the USDC, broadcasts the onchain send, and the funds arrive at the destination address once the transaction confirms.

### Webhook, `transaction.cryptoToCrypto.completed`

```json theme={null}
{
  "id": "whk_evt_01H9Xw2...",
  "event": "transaction.cryptoToCrypto.completed",
  "createdAt": "2024-01-15T10:02:30Z",
  "data": {
    "id": "txn_01H9Xd...",
    "object": "transaction",
    "type": "cryptoToCrypto",
    "status": "completed",
    "subStatus": null,
    "customerId": "cst_01H9Xa...",
    "quoteId": "qt_01H9Xt...",
    "depositAddress": null,
    "virtualAccount": null,
    "cashIn": null,
    "source": {
      "walletId": "wlt_01H9Xb...",
      "asset": "usdc",
      "network": "polygon",
      "amountGross": "2000.00",
      "amountNet": "2000.00",
      "feesDeducted": {
        "total": "0.00",
        "developer": "0.00",
        "oms": "0.00",
        "gas": "0.00"
      },
      "txHash": "0x7f2a9b...c3d4e5"
    },
    "destination": {
      "wallet": {
        "blockchainAddress": "0xAbC1230000000000000000000000000000DeF456"
      },
      "asset": "usdc",
      "network": "polygon",
      "amountGross": "1998.50",
      "amountNet": "1998.50",
      "feesDeducted": {
        "total": "1.50",
        "developer": "0.00",
        "oms": "1.50",
        "gas": "0.00"
      }
    },
    "fixedAmountSide": "source",
    "sponsorGasCost": "0.42",
    "sponsorGas": true,
    "rates": {
      "pair": "usdc/usdc",
      "exchangeRate": "1.0000",
      "effectiveRate": "0.9993"
    },
    "error": null,
    "metadata": null,
    "createdAt": "2024-01-15T10:01:00Z",
    "updatedAt": "2024-01-15T10:02:30Z"
  }
}
```

**What changed from the `processing` webhook:**

* `status` is now `completed` and `subStatus` is `null`.
* `updatedAt` reflects when the onchain send confirmed.

At this point the flow is done. 2,000 USDC was pulled from the custodial wallet, OMS took a $1.50 platform fee, and 1,998.50 USDC arrived at the destination address. The developer absorbed $0.42 in gas costs.

### Polling alternative

If you prefer polling over webhooks, retrieve the transaction directly:

```
GET /v0.9/transactions/txn_01H9Xd...
Authorization: Bearer {token}
```

Poll until `status` is `completed` or `failed`. Webhooks are preferred for production: they avoid unnecessary requests and notify you the moment status changes.

## Failure handling

If the transaction fails after USDC has been pulled (for example, an onchain error or a compliance block), OMS fires a `transaction.cryptoToCrypto.failed` webhook:

```json theme={null}
{
  "id": "whk_evt_01H9Xw3...",
  "event": "transaction.cryptoToCrypto.failed",
  "createdAt": "2024-01-15T10:02:00Z",
  "data": {
    "id": "txn_01H9Xd...",
    "object": "transaction",
    "type": "cryptoToCrypto",
    "status": "failed",
    "subStatus": "failed.sendRejected",
    "error": {
      "code": "send_rejected",
      "message": "Onchain send could not be completed"
    },
    "...rest of transaction object..."
  }
}
```

If OMS successfully refunds the USDC back to the source wallet, you'll receive a `transaction.cryptoToCrypto.refundCompleted` webhook.

## Compliance review

In some cases, a transaction may be held for compliance review. This fires a developer-only webhook (not forwarded to customers):

```json theme={null}
{
  "id": "whk_evt_01H9Xw4...",
  "event": "transaction.cryptoToCrypto.underReview",
  "createdAt": "2024-01-15T10:01:05Z",
  "data": {
    "id": "txn_01H9Xd...",
    "object": "transaction",
    "type": "cryptoToCrypto",
    "status": "processing",
    "subStatus": "processing.underReview",
    "...rest of transaction object..."
  }
}
```

The transaction remains in `processing` during review. Once resolved, it proceeds to `completed` or `failed`.

## Webhook events

| Event                                        | When                                          |
| -------------------------------------------- | --------------------------------------------- |
| `transaction.cryptoToCrypto.processing`      | USDC pulled, onchain send underway            |
| `transaction.cryptoToCrypto.completed`       | Funds delivered to the destination            |
| `transaction.cryptoToCrypto.failed`          | Send failed (onchain error, compliance, etc.) |
| `transaction.cryptoToCrypto.refundCompleted` | USDC refund returned to source wallet         |
| `transaction.cryptoToCrypto.underReview`     | Under compliance review (developer-only)      |

## Key points

* **The quote is the contract.** The transaction request body is just `{ "quoteId": "..." }`: no overrides.
* **The source must be an OMS wallet.** A quote `source` is always `{ "walletId", "asset", "network" }`. Send to another OMS wallet with `destination.wallet.id` or to an external address with `destination.wallet.blockchainAddress`.
* **Set `sponsorGas` to absorb gas for the customer.** Pulling and sending USDC has gas costs on the source side. With `sponsorGas: true`, the customer sees zero source-side fees and the developer absorbs the cost reported in `sponsorGasCost`.
* **`source.txHash` is populated** because OMS executes an onchain transaction to send the USDC.
* **`sponsorGasCost` may differ between quote and transaction.** The quote gives an estimate; the transaction shows the actual cost. Plan for minor variance.
* **No developer fee in this example.** Omitting `developerFees` means the developer takes no cut. Add fee entries to the quote request to charge.
* **Idempotency keys are per request.** Use a distinct `Idempotency-Key` for the quote and the transaction so retries are safe and don't create duplicates.

## Pay out to a bank account

<Note>
  Bank payout destinations are not yet available in the OMS API. To be notified when it launches, register your interest.

  <Card title="Register interest" icon="envelope" href="https://info.polygon.technology/get-early-access?utm_source=docs&utm_medium=card&utm_campaign=oms_access">
    Share your use case and we'll reach out when bank payout destinations are available.
  </Card>
</Note>

A fiat payout follows the same two-step quote and transaction flow. The quote `source` is still an OMS wallet holding USDC. The difference is the destination: instead of a crypto address, you target a saved external bank account using `destination.wallet.externalAccount` (a `cpUsBank_` ID) with `asset: "usd"` and a fiat rail in `network` (`ach` or `wire`). OMS infers the transaction type as `cryptoToFiat`.

External bank accounts have no endpoints yet, so this path is early access.

```json theme={null}
{
  "customerId": "cst_01H9Xa...",
  "source": {
    "walletId": "wlt_01H9Xb...",
    "asset": "usdc",
    "network": "polygon",
    "amount": "2000.00"
  },
  "destination": {
    "wallet": {
      "externalAccount": "cpUsBank_01H9Xs..."
    },
    "asset": "usd",
    "network": "ach"
  },
  "sponsorGas": true
}
```

Once the quote is open, create the transaction the same way, with `{ "quoteId": "..." }`. Track it with `transaction.cryptoToFiat.*` webhooks or by polling `GET /v0.9/transactions/{id}`. ACH transfers typically settle in 1 to 3 business days; `wire` is faster. The same operational notes apply: the quote is the contract, `sponsorGas` covers the source-side gas reported in `sponsorGasCost`, and you should use a distinct idempotency key per request.
