The Grid Sandbox environment provides a complete testing environment for ramp operations, allowing you to validate on-ramp and off-ramp flows without using real money or cryptocurrency.
Sandbox overview
Sandbox mirrors production behavior while using simulated funds:
- Same API endpoints: Use identical API calls as production
- Simulated funding: Mock bank transfers and crypto deposits
- Real webhooks: Receive actual webhook notifications
- No real money: All transactions use test funds
- Isolated environment: Sandbox data never affects production
Sandbox is perfect for development, testing, and demonstrating ramp
functionality before going live.
Getting started
Create sandbox credentials
- Log into the Grid dashboard
- Navigate to Settings → API Keys
- Click Create API Key and select Sandbox environment
- Save your API key ID and secret securely
Sandbox credentials only work with the sandbox environment. They cannot access
production data or move real funds.
Set up a webhook endpoint for sandbox notifications:
curl -X PATCH 'https://api.lightspark.com/grid/2025-10-13/config' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"webhookEndpoint": "https://api.yourapp.dev/webhooks/grid"
}'
Use tools like ngrok to expose local webhook endpoints during development:
ngrok http 3000
Testing on-ramps (Fiat → Crypto)
Simulate the complete on-ramp flow in sandbox:
Step 1: Create a test customer
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"platformCustomerId": "test_user_001",
"customerType": "INDIVIDUAL",
"fullName": "Alice Test",
"email": "alice@example.com",
"birthDate": "1990-01-15",
"address": {
"line1": "123 Test Street",
"city": "San Francisco",
"state": "CA",
"postalCode": "94105",
"country": "US"
}
}'
In sandbox, customers are automatically approved for testing.
Step 2: Create an on-ramp quote (just-in-time funding)
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"source": {
"customerId": "Customer:sandbox001",
"currency": "USD"
},
"destination": {
"externalAccountDetails": {
"customerId": "Customer:sandbox001",
"currency": "BTC",
"accountInfo": {
"accountType": "SPARK_WALLET",
"address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu"
}
}
},
"lockedCurrencySide": "SENDING",
"lockedCurrencyAmount": 10000,
"description": "Test on-ramp conversion"
}'
The quote response includes payment instructions with a reference code.
Step 3: Simulate funding
Use the sandbox endpoint to simulate receiving the fiat payment:
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/sandbox/send' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"reference": "RAMP-ABC123",
"currencyCode": "USD",
"currencyAmount": 10000
}'
The reference code must match the one provided in the quote’s payment
instructions.
Step 4: Verify completion
Within seconds, you’ll receive a webhook notification confirming the on-ramp completed:
{
"transaction": {
"id": "Transaction:sandbox025",
"status": "COMPLETED",
"type": "OUTGOING",
"sentAmount": {
"amount": 10000,
"currency": { "code": "USD" }
},
"receivedAmount": {
"amount": 95000,
"currency": { "code": "BTC" }
},
"settledAt": "2025-10-03T15:02:30Z"
},
"type": "OUTGOING_PAYMENT"
}
Testing off-ramps (Crypto → Fiat)
Simulate the complete off-ramp flow:
Step 1: Fund internal account with crypto
Simulate a Bitcoin deposit to the customer’s internal account using the sandbox funding endpoint:
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/sandbox/internal-accounts/InternalAccount:btc001/fund' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"amount": 10000000
}'
Replace InternalAccount:btc001
with your actual BTC internal account ID.
You’ll receive an ACCOUNT_STATUS
webhook showing the updated balance.
Step 2: Create external bank account
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers/external-accounts' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"customerId": "Customer:sandbox001",
"currency": "USD",
"platformAccountId": "test_bank_001",
"accountInfo": {
"accountType": "US_ACCOUNT",
"accountNumber": "123456001",
"routingNumber": "021000021",
"accountCategory": "CHECKING",
"bankName": "Test Bank",
"beneficiary": {
"beneficiaryType": "INDIVIDUAL",
"fullName": "Alice Test",
"birthDate": "1990-01-15",
"nationality": "US",
"address": {
"line1": "123 Test Street",
"city": "San Francisco",
"state": "CA",
"postalCode": "94105",
"country": "US"
}
}
}
}'
In sandbox, you can use special account number patterns to test different scenarios. The last 3 digits determine the behavior: 002 (insufficient funds), 003 (account closed), 004 (transfer rejected), 005 (timeout/delayed failure). Any other ending succeeds normally. See “Testing transfer failures” below for details.
Step 3: Create and execute off-ramp quote
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"source": {
"accountId": "InternalAccount:sandbox_btc001"
},
"destination": {
"accountId": "ExternalAccount:sandbox_bank001",
"currency": "USD"
},
"lockedCurrencySide": "SENDING",
"lockedCurrencyAmount": 5000000,
"description": "Test off-ramp conversion"
}'
Then execute the quote:
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes/{quoteId}/execute' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET"
In sandbox, off-ramp conversions complete instantly. In production, bank
settlement may take 1-3 business days.
Testing transfer failures
External account test patterns
When creating external bank accounts in sandbox, use special account number patterns to simulate different transfer failure scenarios. The last 3 digits of the account number determine the test behavior:
Last Digits | Behavior | Use Case |
---|
002 | Insufficient funds | Simulates bank account with insufficient balance |
003 | Account closed/invalid | Simulates closed or non-existent account |
004 | Transfer rejected | Simulates bank rejecting the transfer (compliance, limits, etc.) |
005 | Timeout/delayed failure | Transaction stays pending ~30s, then fails |
Any other | Success | All transfers complete normally |
Example - Testing Insufficient Funds:
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers/external-accounts' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"customerId": "Customer:sandbox001",
"currency": "USD",
"accountInfo": {
"accountType": "US_ACCOUNT",
"accountNumber": "000000002", // Will trigger insufficient funds
"routingNumber": "021000021",
"accountCategory": "CHECKING",
"beneficiary": {
"beneficiaryType": "INDIVIDUAL",
"fullName": "Test User"
}
}
}'
When you create an off-ramp quote to this account and execute it, the transaction will fail immediately with an insufficient funds error.
These patterns work for all account types: US account numbers, IBANs, CLABEs, etc. Just ensure the identifier ends with the appropriate
test digits. For scenarios like PIX and UPI, where there’s a domain part involved, append the test digits to the user name part.
For example, if testing email addresses as a PIX key, the full identifier would be “testuser.002@pix.com.br” to trigger the
insufficient funds scenario.
Test scenarios
Successful conversions
The complete on-ramp and off-ramp flows described in the sections above demonstrate successful conversion scenarios. For quick reference:
On-ramp test (USD → BTC):
- Create customer and quote with payment instructions
- Use
/sandbox/send
to simulate funding
- Verify completion via webhook
Off-ramp test (BTC → USD):
- Fund BTC internal account with
/sandbox/internal-accounts/{accountId}/fund
- Create external bank account (use default account number for success)
- Create and execute quote
- Verify completion via webhook
Failed conversions
Test error scenarios systematically using the magic account patterns:
1. Test external account insufficient funds (002):
# Create account with insufficient funds pattern
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers/external-accounts' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"customerId": "Customer:sandbox001",
"currency": "USD",
"accountInfo": {
"accountType": "US_ACCOUNT",
"accountNumber": "000000002",
"routingNumber": "021000021",
"accountCategory": "CHECKING",
"beneficiary": {
"beneficiaryType": "INDIVIDUAL",
"fullName": "Test User"
}
}
}'
# Attempt off-ramp to this account - will fail immediately
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes/{quoteId}/execute' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET"
# Response: 400 Bad Request with insufficient funds error
2. Test account closed (003):
# Create account with closed pattern
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers/external-accounts' \
-d '{"accountNumber": "000000003", ...}'
# Attempt to use - will fail with account closed error
3. Test insufficient balance in internal account:
# Create quote from empty internal account
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"source": {
"accountId": "InternalAccount:empty_btc"
},
"destination": {
"accountId": "ExternalAccount:bank001",
"currency": "USD"
},
"lockedCurrencySide": "SENDING",
"lockedCurrencyAmount": 10000000
}'
# Execute will fail with insufficient balance error
4. Test invalid wallet address:
# Attempt quote with invalid Spark address
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
-u "$SANDBOX_API_KEY:$SANDBOX_API_SECRET" \
-H 'Content-Type: application/json' \
-d '{
"destination": {
"externalAccountDetails": {
"currency": "BTC",
"accountInfo": {
"accountType": "SPARK_WALLET",
"address": "invalid_address"
}
}
}
}'
# Response: 400 Bad Request with validation error
Moving to Production
When you’re ready to move to production:
- Generate production API tokens in the dashboard
- Swap those credentials for the sandbox credentials in your environment variables
- Remove any sandbox-specific test patterns from your code
- Configure production webhook endpoints
- Test with small amounts first
Next steps