The Grid sandbox environment allows you to test your integration without making real payments. When you set up your account, you can configure production and sandbox API tokens. The sandbox token is specifically for testing and development purposes.
It corresponds to a separate platform instance in “sandbox” mode, which can only transact with the sandbox UMA addresses for testing.
Overview
The sandbox environment provides:
- A dedicated sandbox platform for testing
- Test UMA addresses for simulating payments
- Endpoints to simulate sending and receiving payments
- All the same webhooks and flows as production, but with simulated funds
Test UMA Addresses
The sandbox provides several test UMA addresses you can use to simulate different scenarios:
| UMA Address | Description |
|---|
$success.usd@sandbox.uma.money | Always succeeds, sends USD |
$success.eur@sandbox.uma.money | Always succeeds, sends EUR |
$success.mxn@sandbox.uma.money | Always succeeds, sends MXN |
$pending.long.usd@sandbox.uma.money | Simulates a long-pending payment |
$fail.compliance.usd@sandbox.uma.money | Simulates compliance check failure |
Testing Outgoing Payments
To test sending payments from your platform, follow these steps:
- Look up a sandbox UMA address:
curl -X GET "https://api.lightspark.com/grid/2025-10-13/receiver/\$success.usd@sandbox.uma.money" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
- Create a quote as normal:
curl -X POST "https://api.lightspark.com/grid/2025-10-13/quotes" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"lookupId": "Lookup:019542f5-b3e7-1d02-0000-000000000009",
"source": {
"sourceType": "REALTIME_FUNDING",
"currency": "MXN"
},
"destination": {
"destinationType": "UMA_ADDRESS",
"umaAddress": "$success.usd@sandbox.uma.money"
},
"lockedCurrencySide": "SENDING",
"lockedCurrencyAmount": 10000
}'
- Instead of making a real bank transfer, use the sandbox send endpoint:
curl -X POST "https://api.lightspark.com/grid/2025-10-13/sandbox/send" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"reference": "UMA-Q12345-REF",
"currencyCode": "USD",
"currencyAmount": 10000
}'
The sandbox will simulate the payment and send appropriate webhooks just like in production.
Testing Incoming Payments
To test receiving payments to your platform’s users, use the sandbox receive endpoint:
curl -X POST "https://api.lightspark.com/grid/2025-10-13/sandbox/uma/receive" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"senderUmaAddress": "$success.usd@sandbox.uma.money",
"receiverUmaAddress": "$your.user@your.domain",
"receivingCurrencyCode": "USD",
"receivingCurrencyAmount": 5000
}'
This will trigger the same webhook flow as a real incoming payment:
- You’ll receive an
INCOMING_PAYMENT webhook with status: "PENDING"
- Your platform should approve/reject the payment
- On approval, you’ll receive another webhook with
status: "COMPLETED"
Example Testing Flow
Here’s a complete example of testing both directions of payments:
- First, register a test user:
curl -X POST "https://api.lightspark.com/grid/2025-10-13/users" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"umaAddress": "$test.user@your.domain",
"platformUserId": "test_123",
"userType": "INDIVIDUAL",
"fullName": "Test User",
"birthDate": "1990-01-01",
"address": {
"line1": "123 Test St",
"city": "Testville",
"state": "TS",
"postalCode": "12345",
"country": "US"
}
}'
- Test receiving a payment:
curl -X POST "https://api.lightspark.com/grid/2025-10-13/sandbox/uma/receive" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"senderUmaAddress": "$success.usd@sandbox.uma.money",
"receiverUmaAddress": "$test.user@your.domain",
"receivingCurrencyCode": "USD",
"receivingCurrencyAmount": 5000
}'
- Test sending a payment:
# 1. Look up recipient
curl -X GET "https://api.lightspark.com/grid/2025-10-13/receiver/\$success.usd@sandbox.uma.money" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
# 2. Create quote
curl -X POST "https://api.lightspark.com/grid/2025-10-13/quotes" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"lookupId": "Lookup:019542f5-b3e7-1d02-0000-000000000009",
"source": {
"sourceType": "REALTIME_FUNDING",
"currency": "MXN"
},
"destination": {
"destinationType": "UMA_ADDRESS",
"umaAddress": "$success.usd@sandbox.uma.money"
},
"lockedCurrencySide": "SENDING",
"lockedCurrencyAmount": 10000
}'
# 3. Simulate sending payment
curl -X POST "https://api.lightspark.com/grid/2025-10-13/sandbox/send" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"reference": "UMA-Q12345-REF",
"currencyCode": "USD",
"currencyAmount": 10000
}'
Testing Error Scenarios
You can test various error scenarios using the special sandbox UMA addresses:
- Test compliance failures:
curl -X GET "https://api.lightspark.com/grid/2025-10-13/receiver/\$fail.compliance.usd@sandbox.uma.money" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
# ... create quote and attempt payment
- Test long-pending payments:
curl -X GET "https://api.lightspark.com/grid/2025-10-13/receiver/\$pending.long.usd@sandbox.uma.money" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
# ... create quote and attempt payment
- Non-existent UMA address:
curl -X GET "https://api.lightspark.com/grid/2025-10-13/receiver/\$non.existent.usd@sandbox.uma.money" \
-H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
# ... should return 404 Not Found
Each of these will trigger appropriate error webhooks and status updates to help you test your error handling.
Global Account magic values
The Grid sandbox accepts a small set of magic values that bypass real auth and credential checks for Global Account flows, so you can exercise the full request shape without standing up Turnkey, WebAuthn, or an OIDC provider. These values are sandbox-only — production enforces real signature verification, WebAuthn assertion, and OIDC nonce binding.
A wrong magic value (or any other value) returns 401 UNAUTHORIZED with a reason field that names the specific check that failed.
Email OTP code
Pass 000000 as the body otp on POST /auth/credentials/{id}/verify when the credential type is EMAIL_OTP. The sandbox skips OTP delivery and accepts this value as a valid response to the issued challenge.
curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMethod:abc123/verify \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "Request-Id: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
-d '{
"type": "EMAIL_OTP",
"otp": "000000",
"clientPublicKey": "04f45f2a..."
}'
Any other code returns 401 UNAUTHORIZED with reason: "Invalid OTP code".
Passkey assertion signature
Pass sandbox-valid-passkey-signature as assertion.signature on POST /auth/credentials/{id}/verify when the credential type is PASSKEY. The sandbox accepts the rest of the assertion as-is and skips the WebAuthn signature check.
curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMethod:abc123/verify \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "Request-Id: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
-d '{
"type": "PASSKEY",
"assertion": {
"credentialId": "...",
"clientDataJson": "...",
"authenticatorData": "...",
"signature": "sandbox-valid-passkey-signature"
},
"clientPublicKey": "04f45f2a..."
}'
Any other signature returns 401 UNAUTHORIZED with reason: "Invalid passkey signature". clientPublicKey is still required — the magic value bypasses the credential check, not the HPKE plumbing that seals the session signing key to the public key you supply.
OAuth (OIDC) token
Pass sandbox-valid-oidc-token as the body oidcToken on both POST /auth/credentials (OAUTH create) and POST /auth/credentials/{id}/verify (OAUTH).
curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMethod:abc123/verify \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "Request-Id: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
-d '{
"type": "OAUTH",
"oidcToken": "sandbox-valid-oidc-token",
"clientPublicKey": "04f45f2a..."
}'
Any other token returns 401 UNAUTHORIZED with reason: "Invalid OIDC token".
OAUTH create still requires a JWT-shaped token. On the initial POST /auth/credentials (OAUTH create), the oidcToken must be a structurally valid JWT (header.payload.signature) so Grid can decode the iss claim and resolve the provider name. The literal sandbox-valid-oidc-token works on verify but not on create — for create, sign your own dummy JWT with any payload that includes a recognized iss claim. The sandbox bypasses signature verification, not JWT structure parsing.
Pass sandbox-valid-signature as the Grid-Wallet-Signature HTTP header on any signed-retry flow:
POST /auth/credentials (add-additional-credential signed retry)
DELETE /auth/credentials/{id} (revoke credential)
DELETE /auth/sessions/{id} (revoke session)
POST /internal-accounts/{id}/export (export wallet)
POST /quotes/{quoteId}/execute (when source is an embedded wallet)
curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes/Quote:abc123/execute \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
-H "Grid-Wallet-Signature: sandbox-valid-signature"
Any other header value returns 401 UNAUTHORIZED with reason: "Invalid Grid-Wallet-Signature".
Production vs Sandbox
Here are the key differences between production and sandbox environments:
- API Tokens: Sandbox tokens only work in the sandbox environment and vice versa
- Bank Transfers: In sandbox, you use
/sandbox/send instead of real bank transfers
- Test UMA Addresses: Special sandbox addresses for testing different scenarios
- Money: No real money is moved in sandbox
Always test thoroughly in sandbox before moving to production!