# USDC Transfers

A customer funds a pocket by sending USDC to their crypto deposit address, and Jiko credits the pocket **in USD**. A USDC transfer out debits the pocket in USD and the recipient receives USDC on-chain. The customer's balance is always USD, and **Jiko never holds crypto on their behalf**, on either side.

Ethereum is the only supported chain today, and only **customer-owned wallets** are supported. Native ETH is not, and neither are transfers to or from a wallet belonging to someone other than your customer.

Jiko works with Circle for the on-chain leg, and the pipeline runs in both directions without anyone touching it:

- **In.** USDC arrives at the customer's deposit address. Jiko detects it and routes it to Circle, which converts it 1:1 into US dollars. Jiko buys T-Bills with those dollars.
- **Out.** Jiko liquidates T-Bills for the amount, sends the cash to Circle, and Circle mints the equivalent USDC and delivers it to the destination wallet.


The customer's balance is USD at every point in that chain. The blockchain tracking and the broker-dealer execution are automated and run continuously, which is why the timing of a deposit is governed by the network and by screening rather than by a business-day schedule.

> Crypto is under active development. Capabilities, endpoints and constraints described here are the current state, not the planned end state.


div
## Prerequisites

### Feature flags

Feature flags are set by Jiko per partner and evaluated against the API user making the request. A call that needs a flag you do not have fails with `403`. Ask your Jiko point of contact for the ones you need.

Three separate flags cover this product, and they are independent — being able to receive USDC does not imply being able to send it.

| Flag | Needed for | Without it |
|  --- | --- | --- |
| `create_crypto_portal` | Issuing a crypto deposit address portal, which is the only way to receive | `403` on portal creation. The customer has no address to be paid at |
| `create_crypto_counterparty` | Registering a wallet address as a destination | `403` on counterparty creation. You cannot name anywhere to send to |
| `create_crypto_transfer` | `POST /api/v1/transfers/usdc/` | `403` on the transfer. The endpoint is gated at the router, so every call fails regardless of body |


### Everything else

- **An approved customer and a pocket in `OPEN` status.**
- **A destination wallet owned by your customer, to send.** Only customer-owned wallets are supported — not a third party's, and not native ETH. The counterparty must also reach `LINKED` before you can send to it, which takes up to a few minutes while Circle validates the address.
- **A JikoPay pocket.** Crypto deposit address portals are not available on JikoStore. If your partnership can choose pocket types, `allowed_pocket_types` must include `JIKO_PAY`; otherwise confirm the default with your Jiko point of contact.
- **Ethereum.** It is the only supported chain today, which also means one deposit address per customer — see [One address per customer](#one-address-per-customer-and-you-cannot-get-a-second).
- **A client-supplied `id` on both creates.** Crypto portals and crypto wallet counterparties both require you to generate the UUID, unlike virtual bank account portals. There is no server-generated fallback.
- **A portal that has reached `OPEN`, to receive.** A crypto portal is created `PENDING` while Jiko provisions the wallet and runs compliance checks, and there is no webhook for portal status — you must poll. A portal created moments earlier has no usable address.
- **Balance that can actually move, to send.** Read `liquidation_value_t0` and `liquidation_value_t1` on the pocket's portfolio rather than `total_value`.
- **Deposit limits that fit what you expect to receive.** Per-transaction and daily limits are commercial and set by your agreement with Jiko. A deposit over them will not credit automatically, and there is no return path on this rail.
- **Somewhere to persist the create response.** There is no endpoint to list or fetch crypto transfers, so the object returned at creation is your only durable handle on it.
- **A webhook subscription** for `transfers.crypto.deposit.success`, `transfers.crypto.withdrawal.success` and `transfers.crypto.withdrawal.rejected`. Subscriptions are recorded against the API user that creates them. Portal status is the one thing events do not cover.


## Receiving USDC

### Issue the deposit address

```
POST /api/v2/pockets/{pocket_id}/portals/
```

with `type: "CRYPTO_DEPOSIT_ADDRESS_PORTAL"`, a `chain`, a `name` of up to 50 characters, and a **client-supplied `id`** (which makes creation idempotent — crypto portals require it, unlike virtual bank account portals).

```bash
curl -i -X POST \
  'https://your-partner-name.partner-api.jikoservices.com/api/v2/pockets/{pocket_id}/portals/' \
  -H 'Authorization: Bearer <YOUR_TOKEN_HERE>' \
  -H 'Content-Type: application/json' \
  -H 'x-jiko-idempotency: 497f6eca-6276-4993-bfeb-53cbbbba6f08' \
  -d '{
    "id": "8b1a9953-c461-4f2a-9c3e-1a2b3c4d5e6f",
    "name": "USDC deposits",
    "chain": "ETH",
    "type": "CRYPTO_DEPOSIT_ADDRESS_PORTAL"
  }'
```

`chain` is `ETH`. It is the only value supported today.

The portal is created `PENDING`. Circle provisions the wallet address and screens the entity asynchronously, after which the portal moves to `OPEN` or `REJECTED`. A portal created moments earlier has no usable address.

There is **no webhook for portal status**. Poll `GET /api/v2/pockets/{pocket_id}/portals/{portal_id}/` until it leaves `PENDING`.

An open crypto portal returns:

```json
{
  "id": "8b1a9953-c461-4f2a-9c3e-1a2b3c4d5e6f",
  "pocket_id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  "name": "USDC deposits",
  "status": "OPEN",
  "time_created": "2023-01-01T12:00:00Z",
  "type": "CRYPTO_DEPOSIT_ADDRESS_PORTAL",
  "chain": "ETH",
  "wallet_address": "0x..."
}
```

`GET /api/v2/pockets/{pocket_id}/portals/{portal_id}/funding-instructions/` returns the same address and chain as a `USDC` instruction, alongside any other rails the pocket supports.

### One address per customer, and you cannot get a second

A crypto deposit address portal is unique **per customer per chain**. Since Ethereum is the only chain today, that means **one crypto address per customer**. A second pocket for the same customer cannot have its own deposit address.

This is a constraint of Jiko's crypto infrastructure provider, not a design choice: the provider registers each customer once as an identified entity and provisions a single wallet per chain against that registration.

The consequence for your integration is concrete. On the bank rails you can issue a portal per payer, per invoice or per purpose and let `portal_id` do your attribution. **On crypto you cannot.** The customer is the finest attribution unit available, and `ON_CHAIN` transactions do not carry `portal_id` at all. Attribution is by wallet address and transaction hash.

### The address cannot be revoked

Closing the portal stops Jiko routing deposits to the pocket. **The address stays live on the public blockchain** and anyone still holding it can keep sending to it. Those funds are no longer routed anywhere.

Treat a deposit address as permanently exposed once shared. Closing a portal is not a way to stop receiving crypto — tell every payer holding the address to stop using it *before* you close it. On ACH and wires, closing a portal means later payments are returned to the sender, which is a reasonable outcome. Here it is not.

### What to expect on a deposit

A deposit is credited in USD once it has confirmed on-chain and cleared Jiko's checks.

- **Timing is not predictable from the send.** Elapsed time depends on network confirmation as well as Jiko's processing, so a deposit does not appear on a fixed schedule the way a same-day ACH or an in-business-day wire does.
- **A deposit that cannot be credited is not returned.** There is no automatic return path on this rail. Funds sent on-chain cannot be handed back automatically. A deposit that fails a check becomes an operational case handled with Jiko, not a reversal you will see in the API.
- **Per-transaction and daily deposit limits apply.** A deposit exceeding them will not credit automatically. Limits are commercial and set by your agreement with Jiko.


## Sending USDC

### Register the wallet

```
POST /api/v2/customers/{customer_id}/counterparties/
```

with `type: "crypto_wallet_address"`, the destination `wallet_address`, the `chain`, a `counterparty_name`, and a **client-supplied `id`** — required for this type.

Crypto wallet counterparties take **no verification method**. Unlike the bank rails, there is no Plaid, no micro-deposits, no documents, and no verification block on the request at all. The rail is gated per partner instead.

**That does not mean the counterparty is usable immediately.** Circle validates the blockchain address before Jiko will send to it, which takes up to a few minutes, and the counterparty carries the same `status` field as every other type while it happens. Poll `GET /api/v2/customers/{customer_id}/counterparties/{counterparty_id}/` until `status` is `LINKED`. An address that fails validation does not get there.

### Workflow

Partner systems:

1. Collect and check the destination wallet address. There is no recall on this rail, so this is the last point at which an error is correctable.
2. Register it with `type: "crypto_wallet_address"`, a client-supplied `id`, the `chain` and a `counterparty_name`.
3. Poll the counterparty until `status` is `LINKED`.
4. Create the transfer against `counterparty_id`.


### Relevant API endpoints

| Step | Purpose | Endpoint |
|  --- | --- | --- |
| 1 | Register the destination wallet | POST `/api/v2/customers/{customer_id}/counterparties/` |
| 2 | Poll until it reaches `LINKED` | GET `/api/v2/customers/{customer_id}/counterparties/{counterparty_id}/` |
| 3 | Send | POST `/api/v1/transfers/usdc/` |


### Create the transfer

```
POST /api/v1/transfers/usdc/
```

| Field | Notes |
|  --- | --- |
| `pocket_id` | The pocket to debit |
| `counterparty_id` | The crypto wallet counterparty |
| `amount_usdc` | Amount in **USD cents**, not USDC token units |
| `transfer_id` | Client-generated UUID for idempotency. Recommended |


Despite the field name, `amount_usdc` is a USD amount, exactly as on every other rail.

Unlike the wire endpoint, this one **returns the transfer in the response body**:

```json
{
  "id": "…",
  "status": "PROCESSING",
  "pocket_id": "…",
  "counterparty_id": "…",
  "amount": { "amount_usdc": 100000, "currency": "USD" },
  "time_created": "2023-01-01T12:00:00Z"
}
```

### Lifecycle

| Status | Meaning |
|  --- | --- |
| `PROCESSING` | Accepted and in progress |
| `COMPLETED` | Sent on-chain |
| `FAILED` | Did not complete |


The create call is synchronous and returns once the transfer has been accepted, typically in `PROCESSING`. Reaching a terminal status involves compliance screening of the destination address and settlement of the on-chain payout, so `COMPLETED` is not immediate.

**An outbound crypto transfer is irreversible once sent.** There is no recall, no return and no chargeback equivalent. Verify the destination address before instructing — an address error cannot be corrected afterward.

## Webhooks

| Event | Meaning | Payload |
|  --- | --- | --- |
| `transfers.crypto.deposit.success` | An inbound deposit was credited to the pocket | `crypto_transfer_id`, `jiko_account_id` |
| `transfers.crypto.withdrawal.success` | An outbound transfer completed | `crypto_transfer_id`, `jiko_account_id` |
| `transfers.crypto.withdrawal.rejected` | An outbound transfer was rejected | `crypto_transfer_id`, `jiko_account_id` |


There is no event for a deposit that is still confirming on-chain or under review, and none for portal status. Poll the portal until it leaves `PENDING`.

## Reading the money

There is **no endpoint to list or fetch crypto transfers**. `POST /api/v1/transfers/usdc/` is the only USDC endpoint, and it creates outgoing transfers only — there is no inbound crypto resource at all. Until a deposit is credited and appears as a transaction, it has no representation in the Partner API.

**Record the response.** Because there is no list endpoint and no ID-based transaction filter for a crypto transfer, the transfer object returned at creation is your only durable handle on it. Persist it.

Everything else surfaces through the transaction feed:

| Operation | Endpoint |
|  --- | --- |
| Pocket transactions | `GET /api/v2/pockets/{pocket_id}/transactions/` |
| Customer transactions | `GET /api/v2/customers/{customer_id}/transactions/` |
| All partner transactions | `GET /api/v2/transactions/` |
| Single transaction | `GET /api/v2/transactions/{transaction_activity_id}/` |


A credited deposit or a completed withdrawal appears as `type: "ON_CHAIN"`:

- `transaction_hash` — the on-chain transaction, verifiable independently on a block explorer. **This is the strongest end-to-end identifier of any rail**: the payer and you can both check the same record
- `blockchain_network` — the chain, for example `ETH`
- `counterparty_wallet_address` — the other end's address
- `amount` — the USD value credited or debited
- `direction` — `CREDIT` for a deposit, `DEBIT` for a transfer out


Filter with `filter[type]=ON_CHAIN`, plus the usual amount and time-range filters. There is **no** `filter[crypto_transfer]`, so matching the transfer returned by the create call to its transaction means matching on pocket, amount and time.

`ON_CHAIN` transactions do not carry `portal_id`.

## See also

- [Counterparties](/products/partner-api/reference/counterparties-v2/create_counterparty_api_v2_customers__customer_id__counterparties__post)
- [Portals](/products/partner-api/reference/portals-v2/create_portal_api_v2_pockets__pocket_id__portals__post)
- [Funding instructions](/products/partner-api/reference/portals-v2/portal-list-funding-instructions)
- [Webhooks](/products/partner-api/guides/building/webhooks)