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

# Simulate Payin

> Credit a balance in sandbox without a real deposit, and receive the payin.received webhook

Sandbox and development only. Production does not register this route: a call there returns `404` `RESOURCE_NOT_FOUND` in the standard error envelope, and the endpoint is absent from the production OpenAPI document.

Credits a balance as though funds had arrived, so you can exercise the receive side of your integration without a bank transfer or an on-chain send. The payin runs through the same lifecycle a real deposit does, lands `completed`, and raises the same [`payin.received`](/fx/webhooks/events) webhook.

No provider is contacted and no funds are swept. The credit is real money in sandbox terms: it lands on the balance and can fund a payout, a transfer or an FX trade.

Requires `write` on the `payments` scope.

## Path Parameters

<ParamField path="account_id" type="string" required>
  The account's external ID (starts with `acct_`)
</ParamField>

## Headers

<ParamField header="Idempotency-Key" type="string" required>
  Canonical `8-4-4-4-12` UUID, scoped to the account and compared byte-for-byte, as on every idempotent endpoint: `A1B2…` and `a1b2…` are different keys. A replay with the same body returns `200` with the original payin; with a different body, `409` `IDEMPOTENCY_CONFLICT`. See [Idempotency](/fx/api-reference/idempotency).
</ParamField>

## Request Body

<ParamField body="deposit_destination" type="object" required>
  The balance to credit: `{"type": "trading_account"}` or `{"type": "virtual_account", "virtual_account_id": "va_…"}`.

  A virtual account must belong to this account, be `active`, and hold `currency`.
</ParamField>

<ParamField body="currency" type="string" required>
  Currency code. For a `trading_account` destination it must be enabled for payins on the account; a currency that is not returns `422` `CURRENCY_NOT_ENABLED`. A `virtual_account` destination is checked against the virtual account's own currency instead.
</ParamField>

<ParamField body="amount" type="string" required>
  Greater than 0, at most 18 digits and 6 decimal places, and within the currency's own precision. See [Handling currencies](/fx/guides/handling-currencies#decimal-precision).
</ParamField>

<ParamField body="sender_name" type="string">
  Name to record as the sender, max 140 characters. Surfaces as `sender_name` on the payin.
</ParamField>

<ParamField body="note" type="string">
  Remittance reference to record, 1 to 255 characters. Defaults to `Simulated deposit`.
</ParamField>

Unknown fields are rejected with `422` `VALIDATION_ERROR`.

## Response

`201` with the completed [Payin](/fx/api-reference/payins/get-payin) on creation, `200` with the original on an idempotent replay.

The payin is indistinguishable from a real one. Set `sender_name` or `note` if you need to tell simulated credits apart when reconciling a sandbox account.

```json theme={null}
{
  "id": "pi_uh0p51dqek7qes5e52a6x7of",
  "object": "payin",
  "account_id": "acct_ka44qsvpo8q3wtzuwfqf0h6u",
  "amount": "1000.00",
  "currency": "USD",
  "network": null,
  "amount_in_usd": "1000.00",
  "status": "completed",
  "payment_method": null,
  "note": "Simulated deposit",
  "txn_hash": null,
  "destination_wallet_id": null,
  "destination_address": null,
  "source_address": null,
  "sender_name": "ACME TRADING LTD",
  "sender_account_masked": null,
  "sender_bank_code": null,
  "sender_country": null,
  "deposit_destination": { "type": "trading_account" },
  "payin_type": "standard",
  "initiated_at": "2026-01-15T10:00:00Z",
  "completed_at": "2026-01-15T10:00:00Z",
  "failed_at": null, "failed_reason": null, "voided_at": null, "voided_reason": null,
  "created": "2026-01-15T10:00:00Z",
  "updated": "2026-01-15T10:00:00Z",
  "created_by": "ak_3f7k9m2p5r8t1v4x6z0b3n5q",
  "updated_by": null
}
```

## Errors

| Status | `code` | When |
| - | - | - |
| `404` | `RESOURCE_NOT_FOUND` | `virtual_account_id` is unknown or on another account, or the route was called in production, where it is not registered |
| `409` | `IDEMPOTENCY_CONFLICT` | The key was reused with a different body |
| `422` | `VALIDATION_ERROR` | `Idempotency-Key` missing or not a canonical UUID, `amount` not positive or over 6 decimal places, an unknown field, or a value over its maximum length |
| `422` | `INVALID_FIELD_VALUE` | `amount` carries more decimal places than the currency allows |
| `422` | `CURRENCY_NOT_ENABLED` | Trading-account destination in a currency not enabled for payins on the account |
| `422` | `VIRTUAL_ACCOUNT_INACTIVE` | The destination virtual account is not `active` |
| `422` | `CURRENCY_MISMATCH` | `currency` does not match the destination virtual account's currency |

## Examples

<CodeGroup>
  ```json Trading account theme={null}
  {
    "deposit_destination": { "type": "trading_account" },
    "currency": "USD",
    "amount": "1000.00",
    "sender_name": "ACME TRADING LTD",
    "note": "INV-2291"
  }
  ```

  ```json Virtual account theme={null}
  {
    "deposit_destination": {
      "type": "virtual_account",
      "virtual_account_id": "va_7c3e9a1d5b2f8e4a6c0d3b7f"
    },
    "currency": "EUR",
    "amount": "250.00"
  }
  ```
</CodeGroup>

## What this unblocks

Sandbox has no way to make a real deposit arrive, so before this endpoint the receive side could only be exercised against real provider activity. With it you can test:

* A `payin.received` webhook reaching your handler, signed the same way a production delivery is. See [Webhook security](/fx/webhooks/security).
* Funding a sandbox account for a payout, transfer or FX test without asking support to top it up.
* Crediting a named virtual account, including the `deposit_destination` shape your reconciliation reads.
* Fiat credits, which have no sandbox equivalent at all: no bank sends money to a sandbox deposit instruction.

It does not simulate a payout completing or failing, a deposit being returned, or a virtual account activating. Those still need real provider activity.
