Skip to main content
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

  1. Log into the Grid dashboard
  2. Navigate to SettingsAPI Keys
  3. Click Create API Key and select Sandbox environment
  4. 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.

Configure sandbox webhook

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 DigitsBehaviorUse Case
002Insufficient fundsSimulates bank account with insufficient balance
003Account closed/invalidSimulates closed or non-existent account
004Transfer rejectedSimulates bank rejecting the transfer (compliance, limits, etc.)
005Timeout/delayed failureTransaction stays pending ~30s, then fails
Any otherSuccessAll 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):
  1. Create customer and quote with payment instructions
  2. Use /sandbox/send to simulate funding
  3. Verify completion via webhook
Off-ramp test (BTC → USD):
  1. Fund BTC internal account with /sandbox/internal-accounts/{accountId}/fund
  2. Create external bank account (use default account number for success)
  3. Create and execute quote
  4. 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:
  1. Generate production API tokens in the dashboard
  2. Swap those credentials for the sandbox credentials in your environment variables
  3. Remove any sandbox-specific test patterns from your code
  4. Configure production webhook endpoints
  5. Test with small amounts first

Next steps

I