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

# Charge a fee on a deposit

> A deposit carries no margin field because you don't start it — take the margin on the order it funds, or sweep your fee out of the customer's balance yourself

A conversion and a fiat payout both let you [declare your own margin](/guides/take-your-own-margin) on the request, and Conduit collects it for you. A deposit does not, and the reason is structural: **you don't start a deposit.** Someone sends money to your customer's account. There is no request of yours to put a margin on, so no deposit endpoint takes one.

There are two ways to charge on money coming in. Which one you use depends on what the deposit is for.

<Note>
  **Test both in sandbox.** Simulate an inbound deposit and drive the follow-on
  payout with real webhooks — see [deposit simulation](/sandbox/deposits) and
  the [cheat sheet](/sandbox/cheat-sheet).
</Note>

## If the deposit funds a conversion, take the margin on the order

When money arrives to be converted, you create the order, so it is yours to price. That order takes `markupBps` and `markupAmount` like any other, and Conduit collects your margin, accrues it when the order succeeds, and pays it on the monthly statement — exactly as [Take your own margin](/guides/take-your-own-margin) describes.

**Prefer this route whenever it fits.** It costs you one field.

How you name the source depends on the asset.

**Crypto that has not arrived yet.** Omit `source` and send `sourceAsset` instead; Conduit answers with a funding address and the order executes once the funds clear:

```bash theme={null}
curl https://api.conduit.financial/v2/orders \
  -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "idempotency-key: $(uuidgen)" \
  -H "content-type: application/json" \
  -d '{
    "sourceAsset":  { "code": "USDC", "chain": "ethereum" },
    "destination": { "type": "wallet", "id": "wlt_...", "asset": { "code": "USDT", "chain": "tron" } },
    "lockSide": "source",
    "amount": "100",
    "markupBps": 50
  }'
```

Read [Deposit-funded orders](/concepts/deposit-funded-orders) before you build on this; funds that no order claims are returned to the sender.

**Fiat.** `sourceAsset` is for crypto only — a fiat code there returns `400 VALIDATION_ERROR`. Let the wire land in the customer's virtual account first, then create the order naming that account, with the same margin fields:

```bash theme={null}
curl https://api.conduit.financial/v2/orders \
  -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "idempotency-key: $(uuidgen)" \
  -H "content-type: application/json" \
  -d '{
    "source":      { "type": "virtual_account", "id": "vac_034A0gCCW1I7ZiTVLb0G7I" },
    "destination": { "type": "wallet", "id": "wlt_...", "asset": { "code": "USDC", "chain": "ethereum" } },
    "lockSide": "source",
    "amount": "995.00",
    "autoExecute": true,
    "markupBps": 50
  }'
```

Size `amount` off the deposit's `destination.assetAmount` — what was **credited** — and not off what the sender wired. On a source-locked order `totalDebit` equals `amount`, so an order sized at the wire figure is never covered once a deposit fee comes out, and it sits `pending` until `lockExpiresAt` passes and it goes `cancelled` with `cancellationReason: "expired"`.

## If the deposit only credits a balance, sweep your fee

A deposit that simply credits your customer's account has no order attached, so there is nothing to price. To charge here you collect the fee **yourself**: you move it out of the customer's balance into an account you control.

This is a different money model from the rest of the feature, and it is worth being explicit about the difference:

|                         | Declared margin                             | Swept fee                                   |
| ----------------------- | ------------------------------------------- | ------------------------------------------- |
| Who collects it         | Conduit, from your end customer             | You, out of your customer's balance         |
| When you have the money | Paid monthly against a statement            | Immediately, once the sweep settles         |
| Where you read it       | `GET /v2/markup/balances` and the statement | Your own account's balance and transactions |
| What it costs you       | One field on the request                    | One payout per sweep                        |

A swept fee is your own money the moment it lands. It never appears on a markup balance and never on a markup statement.

### Set it up once

1. **Onboard a customer in your own organization to receive the fees.** It is an ordinary customer that you control rather than one of your end users — your fee-collection account. Give it a virtual account in the currency you collect in.
2. **Agree the payout `purpose` with Conduit** before you go live. What a payout needs to clear follows from its `purpose`, and a recurring fee sweep is not the same case as the examples in [Transact](/guides/transact). Ask your Conduit contact which value to send.
3. **Then satisfy what that purpose asks for.** Only `intercompany` uses the whitelist: the recipient must be pre-registered and reach `registered` first, through an asynchronous review that ends in `whitelist_recipient.registered`. That path is built for accounts inside the paying customer's own group and is evidenced as such, so confirm with Conduit that it fits before you build on it. Most other purposes take a supporting document on each payout instead. `prefunding` is the exception that takes neither, and it is not a route for this sweep: its source must be a house account Conduit has marked as one, so it moves money *to* your customers rather than collecting from them. See [Whitelist recipients](/concepts/whitelist-recipients) and [Transact](/guides/transact).

### Then sweep

Listen for `transaction.completed` with `type: "deposit"`. Compute your fee off `destination.assetAmount` — the amount actually credited to the customer, after Conduit's own fee — never off `source.assetAmount`. Then send a payout for it from that customer to your fee-collection account:

```bash theme={null}
curl https://api.conduit.financial/v2/payouts \
  -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "idempotency-key: fee-sweep:vac_034A0gCCW1I7ZiTVLb0G7I:USD:2026-08" \
  -H "content-type: application/json" \
  -d '{
    "customerId": "cus_034A21OFtmes4Wr3HCKgkH",
    "virtualAccountId": "vac_034A0gCCW1I7ZiTVLb0G7I",
    "assetAmount": { "code": "USD", "amount": "4.50" },
    "destination": { "type": "fiat", "rail": "fedwire", "recipient": { "...": "..." } },
    "purpose": "...",
    "clientReferenceId": "fee-sweep:vac_034A0gCCW1I7ZiTVLb0G7I:USD:2026-08"
  }'
```

`recipient` carries the bank details of your fee-collection account's virtual account — the full shape is in [Send a payout](/guides/send-payout#request-body).

<Warning>
  **Derive the `idempotency-key` from what you are sweeping, and persist it.**
  A webhook can be delivered more than once, and your own process can crash
  between sending the payout and recording that it was sent. A fresh key on each
  delivery turns both into a second sweep, and your customer pays the fee twice;
  a stable key makes the replay return the one payout that was accepted. Verify
  the webhook signature before you act on it, too — see [Webhooks](/webhooks).

  **Key it to every dimension a batch has**, as above: the source virtual
  account, the asset and the period. One payout debits one virtual account, so
  a key scoped to the customer and month alone collides the moment that customer
  is swept in a second currency — and that request either conflicts or replays
  the first sweep. Sweeping one deposit at a time? Use its transaction id.
</Warning>

When the recipient resolves to a Conduit-issued account in your own organization, the payout is **delivered internally** — no wire leaves Conduit. Your fee-collection account receives it as a deposit whose `source` carries `type: "internal_transfer"` and an `originatingTransactionId` naming the payout that sent it, and with no wire references at all. That link is how you reconcile a swept fee back to the deposit that earned it — fetch the payout it names for the sending side.

`clientReferenceId` is your own reference on the **payout**, so it identifies a sweep in your records and on every read of that payout. It does not travel to the receiving deposit, which is not client-initiated and carries no reference of yours. On the wire path nothing on that deposit points back at the payout, so match it on amount and arrival.

<Warning>
  Internal delivery is not guaranteed — it is what Conduit does when it can
  recognize the destination, and it falls back to an ordinary wire whenever it
  cannot. It needs **at least** a US-rail recipient whose routing and account
  numbers name exactly one active issued account, in the payout's own currency,
  belonging to a **different customer** — one customer's own two accounts never
  settle internally, which is why the fee-collection account above is its own
  customer. A SWIFT or SEPA recipient never qualifies. Treat the wire path as
  the one you must handle. A wired deposit carries no
  `originatingTransactionId`, and its `source` reads `external_unknown` until
  the sending bank's details arrive, then `external_bank_inbound`.
</Warning>

<Tip>
  **Sweep in batches.** One payout covering a day's or a week's deposits costs
  one payout instead of one per deposit, and one `clientReferenceId` then names
  the whole batch on your side. Sweeping per deposit is supported; it is rarely
  worth it.
</Tip>

You can declare a margin on the sweep payout itself, but there is usually no reason to: the whole payout is already your fee, and a margin on it would be Conduit collecting a second charge from your customer on top.

## Crypto deposits

Crypto that lands in a customer's wallet and stays there cannot be swept the same way. Moving it out is a crypto payout, and under the default custody model that needs **that customer's** signers to approve each one — which is also why a crypto payout takes no margin field at all.

So on crypto, charge where you have a request of your own: convert the deposit and take the margin on the conversion, as in the first section of this page.

## See also

* [Take your own margin](/guides/take-your-own-margin) — the margin field on every flow that has one.
* [Deposit-funded orders](/concepts/deposit-funded-orders) — converting money that has not arrived yet.
* [Money movement](/guides/money-movement-lifecycle) — inbound fiat, end to end.
* [Transact](/guides/transact) — what each payout `purpose` requires.
