Test a card in sandbox
Issue, fund, freeze and change the limit of a sandbox card, and test the purchase, decline and withdrawal screens that sandbox cannot produce today.
Use this recipe to issue, fund, freeze and change the limit of a sandbox card, and to test the purchase, decline and withdrawal screens that sandbox cannot produce today.
Who it is for. An expense-card app that wants to show employees their card, its balance and its status changes before going live.
Status. Available for issuing, funding, reading and updating a virtual card. Not yet available in sandbox for purchases, declines, withdrawals from new cards and physical cards.
Money example
- Gravv issues Ada's sandbox card during activation, with a 100 USD daily limit.
- You fund it with 100 USD from a sandbox source on
polygon. When the transfer iscompleted, the card'sbalancereads 100. - You freeze it with
{"status": "freeze", "limit": 100}. While the card is frozen,balancereads 0, and Gravv sendscards.status.update. - You cannot make it spend 60 USD in sandbox today, so you test the purchase screen by posting a recorded
cards.transaction.paymentpayload to your own endpoint.
Before you start
- You have a sandbox key and your sandbox card account. See Set up your card account.
- You have a sandbox cardholder who is already verified. Sandbox KYC does not complete for new customers today. See Verify a customer in sandbox.
Find out what is required
| Step | In sandbox |
|---|---|
| Card application | Approved at once |
| First card | Issued during feature activation, with a daily limit equal to estimated_monthly_limit |
| Funding | A transfer to the card from a source on polygon. The balance follows the card's wallet |
A daily limit of 0 |
Means no limit in sandbox. In live, it declines every payment |
| Purchases and declines | Not yet available |
| Withdrawal from a card issued since 14 September 2026 | Not yet available |
| Physical cards | Issuance fails with card creation failed! |
Steps
1. Issue the card
Follow Issue a virtual card after a hosted check, with your sandbox key and your verified cardholder.
2. Fund it
Follow Top up a card. In sandbox, a funding transfer sends only transfer.status.pending events. Poll the transaction until it is completed, then read the balance.
curl --request GET \
--url https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/balance \
--header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/balance", {
method: "GET",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
},
});
console.log(response.status, await response.json());
import os
import requests
response = requests.get(
"https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/balance",
headers={
"Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())
3. Freeze it and change its limit
Always send status and limit together.
curl --request PATCH \
--url https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/update \
--header 'Api-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--data '
{
"status": "freeze",
"limit": 100
}
'
const response = await fetch("https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/update", {
method: "PATCH",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"status": "freeze",
"limit": 100
}),
});
console.log(response.status, await response.json());
import os
import requests
response = requests.patch(
"https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/update",
headers={
"Api-Key": os.environ["GRAVV_API_KEY"],
"Content-Type": "application/json",
},
json={
"status": "freeze",
"limit": 100,
},
)
print(response.status_code, response.json())
Unfreeze with {"status": "active", "limit": 100}. Gravv sends cards.status.update for each real status change.
4. Test the purchase and decline screens without a purchase
You cannot produce card purchases in sandbox today. To test your handler:
- Post a recorded payload, such as the one in Card decline reasons, to your own endpoint. If your endpoint checks signatures, sign the payload yourself with your sandbox secret.
- Or resend a stored event that your endpoint already received. Gravv does not sign a resend.
What can go wrong
| Response | Why | Fix |
|---|---|---|
400 card creation failed! |
You requested a physical card in sandbox | Test virtual cards |
400 this card withdraws by transfer: send a transfer from the card to its account |
You tried to withdraw from a new card | Not yet available. Test the withdraw screen with a recorded payload |
400 the balance for this account could not be confirmed — please try again shortly |
You sent a transfer from a new card to its card account | Not yet available. Do not retry in a loop |
The daily limit reads 0 after an update |
You sent the update without limit |
Send status and limit together |
The card's balance does not move after a funding transfer |
The transfer is not completed yet |
Poll the transaction |
400 card does not exist |
The id is a live card id, or belongs to another business's card | Use a sandbox card id from GET /v1/cards |
Webhooks you will receive
You receive cards.status.update for each real status change. You do not need cards.application.update in sandbox, because sandbox approves the application at once. You receive no purchase events, because sandbox cannot produce purchases.
Testing in sandbox
This page is the sandbox guide for cards. Sandbox also cannot produce refunds, reversals, settlement or foreign-currency purchases.