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

# Pay-ins

> The pay-in ledger — every fiat credit that arrived, what it converted to, and what reached the account wallet — and how it relates to virtual bank accounts, pay-in sessions and webhooks.

Fiat reaches an account in two ways: a bank transfer to the account's
[virtual bank account](/guides/finance/virtual-bank-accounts), or a hosted
[pay-in session](/api-reference/Finance-API/fiat-to-crypto-payment-sessions/create-a-fiat-to-crypto-payment-session)
the payer completes online (**coming soon** — not yet available, so a virtual bank account is the way in today). Either way, what happens next is the same: the fiat is converted and
crypto is credited to the account wallet. That settlement is a **pay-in**, and since 1.8.0 you can read
it.

## Three objects, one deposit

| Object | Answers | Read it at |
| - | - | - |
| **Virtual bank account** | Where can fiat be sent? | [List / get virtual bank accounts](/api-reference/Finance-API/virtual-bank-accounts/list-virtual-bank-accounts) |
| **Pay-in session** | Was the hosted payment initiated and paid? | [List / get pay-in sessions](/api-reference/Finance-API/fiat-to-crypto-payment-sessions/list-pay-in-sessions) |
| **Pay-in** | Did the money settle, into what, and how much arrived? | [List / get pay-ins](/api-reference/Finance-API/pay-ins/list-pay-ins) |

A bank transfer to a virtual bank account produces a pay-in and no session. A hosted payment produces a
session **and** a pay-in — the session tracks initiation, the pay-in tracks settlement.

<Warning>
  **A pay-in session never reaches a "completed" status.** On the happy path it stops at
  `PAYMENT_RECEIVED`. The statuses `CONVERTING`, `MINTING` and `COMPLETED` were removed in 1.8.0 — if
  you poll a session waiting for one of them, you will wait forever. Settlement lives on the pay-in, and
  `PAY_IN_SETTLED` is the event that announces it.
</Warning>

## The pay-in record

```bash theme={null}
curl https://api-staging.venlyfinance.com/v1/pay-ins/{payInId} \
  -H "Authorization: Bearer {access_token}"
```

```json Response (200) theme={null}
{
  "success": true,
  "result": {
    "id": "0c1d2e3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
    "accountId": "b2a1f0e9-8c7d-4e3a-9f21-0a1b2c3d4e5f",
    "virtualBankAccountId": "4d5e6f70-8192-4a3b-9c4d-5e6f7081920a",
    "accountWalletId": "9f8e7d6c-5b4a-4938-8271-6a5b4c3d2e1f",
    "status": "SETTLED",
    "chain": "BASE",
    "cryptoAsset": "USDC",
    "fiatCurrency": "EUR",
    "grossFiatAmount": "100.00",
    "grossCryptoAmount": "108.42",
    "netCryptoAmount": "108.42",
    "transactionHash": "0x4f7a2c9e18b3d05a6c7e94f120d8b3a5e6f1c07d92b4a8e35f6c1d0b7a92e438",
    "settledAt": "2026-08-19T09:14:52Z",
    "createdAt": "2026-08-19T09:02:10Z",
    "updatedAt": "2026-08-19T09:14:52Z"
  }
}
```

| Field | Why it matters |
| - | - |
| `id` | The same `payInId` the `PAY_IN_SETTLED` / `PAY_IN_FAILED` webhooks carry — the join key. |
| `netCryptoAmount` | **What reached the account wallet.** Reconcile against this. |
| `grossCryptoAmount` | The deposit as received. Today always equal to `netCryptoAmount`. |
| `grossFiatAmount` | The fiat received. Informational, and absent for some deposit types. |
| `transactionHash` | The on-chain credit, once broadcast. |
| `accountId` | Absent while the deposit is not yet attributed to an account. Historical records keep it even after the account or virtual bank account is closed. |

Amounts are decimal strings — never floats.

## Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> RECEIVED: fiat credit observed
    RECEIVED --> SETTLING
    SETTLING --> SETTLED
    SETTLING --> FAILED
    RECEIVED --> RETURNED
```

| `status` | Meaning | Event |
| - | - | - |
| `RECEIVED` | The fiat credit was observed. | — |
| `SETTLING` | Conversion and the on-chain credit are in progress. | — |
| `SETTLED` | The crypto is in the account wallet. | `PAY_IN_SETTLED` |
| `FAILED` | The deposit ended without crediting the account. No `PAY_IN_SETTLED` will follow. | `PAY_IN_FAILED` |
| `RETURNED` | The fiat was sent back to the payer. | — |

Build on the webhooks and use the reads for reconciliation and recovery — listing the ledger with
`status=RECEIVED` or `SETTLING` older than your expected settlement time is a cheap stuck-deposit
check.

## Listing the ledger

[`GET /pay-ins`](/api-reference/Finance-API/pay-ins/list-pay-ins) spans every account in your company.
All filters combine with AND:

| Filter | Use |
| - | - |
| `accountId` | One account's deposits |
| `virtualBankAccountId` | Everything that arrived on one set of bank details — including after you closed it |
| `status` | Stuck or failed deposits |
| `from` / `to` | A reconciliation window on `createdAt` — inclusive / exclusive |

Sorted by `createdAt` descending; `sortOn` accepts only `createdAt`.

## Reading sessions back

Before 1.8.0 a pay-in session could not be read after creation. Now
[List pay-in sessions](/api-reference/Finance-API/fiat-to-crypto-payment-sessions/list-pay-in-sessions)
and [Get a pay-in session](/api-reference/Finance-API/fiat-to-crypto-payment-sessions/get-a-pay-in-session)
return the persisted state — never a live provider call — so you can render a deposits view or recover
a session whose `callbackUrl` notification was missed.

To poll for changes, sort `updatedAt` descending and read **only the first page**, sized above your
expected change volume. `from`/`to` filter on `createdAt`, so there is no "changed since" filter, and a
session that changes between your page-1 and page-2 requests shifts position and is skipped.

## Errors worth knowing on session creation

1.8.0 added preconditions to
[creating a hosted pay-in session](/api-reference/Finance-API/fiat-to-crypto-payment-sessions/create-a-fiat-to-crypto-payment-session)
when the account settles through a EUR virtual bank account:

| HTTP | `code` | Meaning |
| - | - | - |
| `409` | `vba-not-ready` | The account's EUR virtual bank account is still being provisioned. **Retryable.** |
| `422` | `vba-required` | The account needs an active EUR virtual bank account first — [create one](/api-reference/Finance-API/virtual-bank-accounts/create-a-virtual-bank-account). |
| `422` | `ambiguous-eur-vba` | More than one active EUR virtual bank account; [close](/api-reference/Finance-API/virtual-bank-accounts/close-a-virtual-bank-account) the surplus. |
| `422` | `vba-asset-mismatch` | The virtual bank account settles into a different asset than the session's `outCryptocurrency`. |
| `422` | `unsupported-currency` | Hosted pay-ins are currently EUR only. |

## Next steps

<CardGroup cols={2}>
  <Card title="Virtual bank accounts" icon="building-columns" href="/guides/finance/virtual-bank-accounts">
    Issuing — and now closing — the bank details pay-ins arrive on.
  </Card>

  <Card title="Webhooks" icon="bell" href="/guides/finance/webhooks">
    `PAY_IN_SETTLED` and `PAY_IN_FAILED`, and how to debug a delivery you didn't get.
  </Card>

  <Card title="Wallets & balances" icon="wallet" href="/guides/finance/wallets">
    Where `netCryptoAmount` lands.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.