Make a test deposit land
Simulate an incoming bank deposit on a sandbox account so the real pipeline runs, and know which accounts can take one today.
Use this recipe to simulate an incoming bank deposit on a sandbox account, so the real deposit pipeline runs, and to find out which accounts can take one today.
Who it is for. A payroll app that opens accounts for freelancers and needs to see deposits, fee rows and webhooks before going live.
Status. Available for NGN accounts. Not yet available for USD, EUR and GBP accounts, because they do not open in sandbox today.
Money example
- Chidi's sandbox NGN account is
activewith test bank details. - You simulate a 50,000.00 NGN deposit into it.
- Gravv credits the account, writes an
account_fundingrow and sendstransfer.inflow.completed. GET /v2/accounts/{id}shows the new balance.
Before you start
- A sandbox key. If you use a scoped key, it needs
transfer:initiate. - An
activesandbox account. Today, these rules apply:- Sandbox KYC does not complete, so a new sandbox customer cannot open any account. Ask support for an already verified sandbox customer.
- For a verified customer, an NGN account opens within seconds.
- USD, EUR and GBP accounts go to review in sandbox and stay
pending.
Find out what is required
railis optional. It takesach,wire,rtp,swift,sepaorfps, and it sets the rail and so the fee. Leave it out for NGN.amountis a string with at most 2 decimal places.currencydefaults to the account's currency.- If the account's bank has no simulator, the call returns an error that says so.
Steps
1. Simulate the deposit
curl --request POST \
--url https://api.gravv.xyz/v2/accounts/simulate/deposit \
--header 'Api-Key: <API_KEY>' \
--header 'Idempotency-Key: 731f71da-768f-43ef-a78a-63ba4f793434' \
--header 'Content-Type: application/json' \
--data '
{
"account_id": "8d0e6b4a-2f1c-4e3d-9a5b-7c6d5e4f3a21",
"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": "731f71da-768f-43ef-a78a-63ba4f793434",
"Content-Type": "application/json",
},
body: JSON.stringify({
"account_id": "8d0e6b4a-2f1c-4e3d-9a5b-7c6d5e4f3a21",
"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": "731f71da-768f-43ef-a78a-63ba4f793434",
"Content-Type": "application/json",
},
json={
"account_id": "8d0e6b4a-2f1c-4e3d-9a5b-7c6d5e4f3a21",
"amount": "50000.00",
"currency": "NGN",
},
)
print(response.status_code, response.json())
{
"data": {
"accepted": true,
"reference": "d3b07384-d9a0-4c8f-9e2b-6f1a2c3d4e5f",
"state": "pending"
},
"error": null
}
accepted means Gravv received the deposit, not that it credited it. Each new Idempotency-Key makes a new deposit. The same key returns the same reference.
2. Wait for the webhook
The webhook depends on the account:
- For an account whose money is held at a bank, Gravv sends
transfer.inflow.completed. - For a wallet-backed account, Gravv sends
transfer.inflow.deposit_settled. - For an account that holds deposits for approval, Gravv sends
transfer.inflow.awaiting_approvalfirst. Approve the deposit in the Dashboard.
In the event, session_id is the simulator's reference, and transaction_id is the row's id.
3. Read the balance and the row
Read the balance from GET /v2/accounts/{id}, not from the row. A simulated deposit row shows balance_before and balance_after as "0". To read the row and its fee, see See a deposit arrive.
For a step-by-step version with every field, see Test accounts with a simulated deposit.
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 with required_any ["transfer:initiate"] |
The scoped key lacks the permission | Add the permission to the key |
400 invalid request payload: cannot unmarshal number … |
You sent amount as a number |
Send "50000.00" |
400 amount must be a positive decimal number |
The amount is zero or negative | Send a positive decimal string |
400 amount cannot be expressed on this account's receiving rail |
The amount has more than 2 decimal places | Send at most 2 decimal places |
400 the simulated deposit was not recorded; the amount may be below what this account's rail credits |
The amount is too small to be credited | Send a larger amount |
400 rail must be one of ach, wire, rtp, swift, sepa, fps |
The rail is unknown | Send a listed rail |
400 account has no linked virtual account to deposit into |
The account is not active yet |
Wait for applications.accounts.completed |
400 deposit simulation is not supported for this account's receiving rail |
This account has no simulator | Test with an NGN v2 account |
404 Resource not found |
The id is wrong, is a live id, or belongs to another business | Use your sandbox account id |
Webhooks you will receive
transfer.inflow.completedon an account whose money is held at a bank.transfer.inflow.deposit_settledwithinflow.onchain.completedon a wallet-backed account.transfer.inflow.awaiting_approvalfirst, on accounts that hold deposits for approval.
Testing in sandbox
The deposit simulator works only in sandbox. There are no special amounts. Every valid deposit succeeds. You cannot produce a returned or failed deposit, so test those screens with recorded transfer.inflow.payout_failed and transfer.inflow.compensated payloads.