Test accounts with a simulated deposit
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.
- You call simulate with
"amount": "250.00"and"rail": "wire". - Her account is wallet-backed, so sandbox first delivers test USDC to its wallet, then records the bank deposit.
- 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 ownfee_chargerow.
Before you start
- You use a sandbox key (
grvSec_sandbox_). A scoped key needstransfer:initiate. - You have an
activesandbox account with bank details. Check it withGET /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.
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.
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"
}
'
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());
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())
{
"data": {
"accepted": true,
"reference": "d3b07384-d9a0-4c8f-9e2b-6f1a2c3d4e5f",
"state": "pending"
},
"error": null
}
To simulate an NGN deposit, send currency and no rail:
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"
}
'
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());
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())
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.
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, thentransfer.inflow.deposit_settled(ortransfer.inflow.completed). - Fiat-only account. You receive
transfer.inflow.completed. - Deposit held for approval. You receive
transfer.inflow.awaiting_approvalfirst.
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.