---
title: "Test accounts with a simulated deposit"
description: "Send a fake incoming deposit to a sandbox account and see the real fees, ledger entries, webhooks and balance."
---
Send a simulated incoming deposit to a sandbox account, and see the real fees, ledger entries, webhooks and balance that it produces.

**API version:** v2.

**Who it is for.** A developer who needs deposit webhooks and balances in sandbox without a real bank transfer.

**Status.** Available in sandbox only.

## Money example

You simulate a 250.00 USD deposit into Ada's sandbox account.

1. You call simulate with `"amount": "250.00"` and `"rail": "wire"`.
2. Her account is wallet-backed, so sandbox first delivers test USDC to its wallet, then records the bank deposit.
3. You receive `transfer.inflow.deposit_settled`, and the balance rises by the full 250.00 USD. Gravv bills any deposit fee on your plan to your business separately, on its own `fee_charge` row.

## Before you start

- You use a **sandbox** key (`grvSec_sandbox_`). A scoped key needs `transfer:initiate`.
- You have an `active` sandbox account with bank details. Check it with `GET /v2/accounts/{id}`. Sandbox KYC does not complete today. The only account you can usually open is an NGN account, which sandbox opens at once. See [Test accounts in sandbox](/platform/accounts/test-accounts-in-sandbox).

## Find out what is required

The `rail` you send depends on the account's currency and its option's `rails`:

| Account | `rail` |
| :-- | :-- |
| USD | `ach`, `wire`, `rtp` or `swift`, from the option's `rails` |
| EUR | `sepa` |
| GBP | `fps` |
| NGN, KES | Leave `rail` out. For NGN, send `"currency": "NGN"` and an amount in naira |

## Steps

### 1. Simulate the deposit

Each new key makes a new deposit. The same key returns the same `reference`.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v2/accounts/simulate/deposit \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: 755660af-95ef-468d-9fd2-cefd7c7e4c4f' \
     --header 'Content-Type: application/json' \
     --data '
{
  "account_id": "6f1c2a9e-3b1d-4f7a-9c62-0d8e4b7a5c11",
  "amount": "250.00",
  "rail": "wire"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v2/accounts/simulate/deposit", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Idempotency-Key": "755660af-95ef-468d-9fd2-cefd7c7e4c4f",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "account_id": "6f1c2a9e-3b1d-4f7a-9c62-0d8e4b7a5c11",
    "amount": "250.00",
    "rail": "wire"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v2/accounts/simulate/deposit",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Idempotency-Key": "755660af-95ef-468d-9fd2-cefd7c7e4c4f",
        "Content-Type": "application/json",
    },
    json={
        "account_id": "6f1c2a9e-3b1d-4f7a-9c62-0d8e4b7a5c11",
        "amount": "250.00",
        "rail": "wire",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "accepted": true,
    "reference": "d3b07384-d9a0-4c8f-9e2b-6f1a2c3d4e5f",
    "state": "pending"
  },
  "error": null
}
```

To simulate an NGN deposit, send `currency` and no `rail`:

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v2/accounts/simulate/deposit \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: a744cbfa-39bb-4923-a45b-cbf2fe937e1a' \
     --header 'Content-Type: application/json' \
     --data '
{
  "account_id": "2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f",
  "amount": "50000.00",
  "currency": "NGN"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v2/accounts/simulate/deposit", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Idempotency-Key": "a744cbfa-39bb-4923-a45b-cbf2fe937e1a",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "account_id": "2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f",
    "amount": "50000.00",
    "currency": "NGN"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v2/accounts/simulate/deposit",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Idempotency-Key": "a744cbfa-39bb-4923-a45b-cbf2fe937e1a",
        "Content-Type": "application/json",
    },
    json={
        "account_id": "2d4f6a8c-0e1b-4c3d-9e5f-7a9b1c3d5e7f",
        "amount": "50000.00",
        "currency": "NGN",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

### 2. Wait for the webhooks

`accepted` does not mean that Gravv credited the deposit. Wait for the deposit webhooks, as in [Share account details and receive a deposit](/recipes/accounts-v2/share-account-details-and-receive-a-deposit#2-credit-the-customer-when-the-deposit-lands).

### 3. Read the balance

Read the account with `GET /v2/accounts/{id}`. `ledger_balance.projected` rises first, then `posted`.

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 403 `Forbidden: insufficient permissions to perform this action` | You used a live key | Use a `grvSec_sandbox_` key |
| 403 `{"reason": "forbidden", "required_any": ["transfer:initiate"]}` | A scoped key without the permission | Add it |
| 400 `invalid request payload: cannot unmarshal number into … amount of type string` | You sent `amount` as a number | Send a string, such as `"250.00"` |
| 400 `rail must be one of ach, wire, rtp, swift, sepa, fps` | You sent an unknown rail | Send a listed rail |
| 400 `account has no linked virtual account to deposit into` | The account is not `active` yet | Wait for activation |
| 400 `account has no wallet to deliver a simulated deposit to` | The wallet-backed account has no wallet | Open a new account on another network |
| 400 `amount cannot be expressed on this account's receiving rail` | The amount has too many decimals | Send at most 2 decimals |
| 400 `the simulated deposit was not recorded; the amount may be below what this account's rail credits` | The amount is too small | Send a larger amount |
| 400 `deposit simulation is not supported for this account's receiving rail` | This account has no simulator | Test with another option |
| 404 `Resource not found` | The `account_id` is wrong, live or not yours. The API looks up the account before it checks the amount | Send a sandbox account id |
| The same `reference` comes back | You reused the same key | Send a new key for each test deposit |

## Webhooks you will receive

- **Wallet-backed account.** You receive `inflow.onchain.completed`, then `transfer.inflow.deposit_settled` (or `transfer.inflow.completed`).
- **Fiat-only account.** You receive `transfer.inflow.completed`.
- **Deposit held for approval.** You receive `transfer.inflow.awaiting_approval` first.

## Testing in sandbox

This recipe is the sandbox tool. No special test value changes its outcome. NGN accounts open at once in sandbox. For the other options, sandbox calls the bank's test system, so they do not open at once.

## Reference

- [Simulate a deposit](/api-reference/accounts-v2/post-v2-accounts-simulate-deposit)
- [Test accounts in sandbox](/platform/accounts/test-accounts-in-sandbox)
- [Fund account wallets during testing](/platform/accounts/fund-account-wallets-during-testing)
