Skip to main content
The Grid sandbox lets you exercise the full Global Accounts integration — customer creation, account lookup, credential registration, funding, and signed withdrawals — without moving real money or standing up real auth providers. All API endpoints work the same way as in production, but money movements are simulated and real auth checks are bypassed via a small set of magic values.

Sandbox setup

To use the sandbox environment:
  1. Go to app.lightspark.com, create an account, and generate your sandbox API keys from the dashboard.
  2. Add your sandbox API token and secret to your environment variables.
  3. Use the normal production base URL: https://api.lightspark.com/grid/2025-10-13.
  4. Authenticate using your sandbox token with HTTP Basic Auth.
In sandbox, customers are KYC-approved on creation, and a Global Account is provisioned automatically alongside any other internal accounts whenever the platform has USDB in its supported currencies.

Funding a Global Account

Real Global Accounts are funded by following payment instructions or by executing a quote into the account. In sandbox, you can instantly add USDB to any internal account using the sandbox funding endpoint:
curl -X POST "$GRID_BASE_URL/sandbox/internal-accounts/InternalAccount:abc123/fund" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 100000
  }'
This credits the account immediately and fires the standard INCOMING_PAYMENT webhook. Use this to skip straight to a funded state when you’re testing withdrawals.

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. Passkey reauthentication is a two-step /challenge/verify flow. The clientPublicKey is sent on /challenge (so Grid can seal the session signing key to your device) — the magic value bypasses the credential check, not the HPKE plumbing, so the public key is still required.
# 1. /challenge with clientPublicKey
curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMethod:abc123/challenge \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "clientPublicKey": "04f45f2a..."
  }'

# 2. /verify with the magic signature, no clientPublicKey
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"
    }
  }'
Any other signature returns 401 UNAUTHORIZED with reason: "Invalid passkey signature".

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.

Wallet signature header

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".

Webhooks

All webhook events fire normally in sandbox. Configure your webhook URL in the dashboard, perform any signed action, and your endpoint receives the same INCOMING_PAYMENT, OUTGOING_PAYMENT, and account-state events as production.
Do not try to send real money to any sandbox addresses or accounts. These are not real destinations and will not receive funds.

Moving to production

When you’re ready to go live:
  1. Generate production API tokens in the dashboard and swap them for the sandbox credentials in your environment.
  2. Remove any sandbox magic values from your client and server code — production runs the real OTP, HPKE, WebAuthn, and ECDSA flows.
  3. Configure production webhook endpoints.
  4. Test with small amounts first.

Next steps