Skip to main content

Documentation Index

Fetch the complete documentation index at: https://ramps-docs-sync-20260602.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

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. OTP, passkey, and wallet signatures use sandbox-only magic values, while OAuth uses JWT-shaped sandbox OIDC tokens with claim, freshness, identity, and nonce checks.

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 lets you exercise Global Account auth flows without moving real money. Email OTP uses the fixed sandbox code 000000. Passkey auth can use the same browser WebAuthn ceremony as production, and signed wallet actions can use the same decrypted session signing key and Grid-Wallet-Signature stamp as production. OAuth uses JWT-shaped sandbox OIDC tokens: sandbox skips real IdP signature verification, but still validates token claims, freshness, credential identity, and verify-time nonce binding. Sandbox-only compatibility values are still available for some flows, but they do not exercise the production-shaped client implementation. Authentication failures return 401 UNAUTHORIZED with a reason field that names the specific check that failed. A malformed OIDC JWT can return 400 INVALID_INPUT before authentication starts.

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 WebAuthn ceremony

For new sandbox integrations, use the same WebAuthn calls you plan to use in production.
1

Create a WebAuthn credential

Generate your own WebAuthn registration challenge and call navigator.credentials.create().
2

Register the passkey

Register the passkey with POST /auth/credentials, passing the challenge and attestation returned by the browser.
3

Request a challenge

Reauthenticate with POST /auth/credentials/{id}/challenge, passing the P-256 clientPublicKey that Grid should seal the session signing key to.
4

Run the browser assertion

Pass the returned challenge into navigator.credentials.get() using the returned credentialId in allowCredentials.
5

Verify the assertion

Verify with POST /auth/credentials/{id}/verify, passing the browser assertion and echoing Request-Id from the challenge response.
The sandbox validates the registered credential ID, WebAuthn challenge, origin/RP binding, user-presence bit, assertion signature, and signature counter. A successful verify response includes encryptedSessionSigningKey, sealed to the clientPublicKey, just like production.
# 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 browser assertion returned by navigator.credentials.get()
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": "..."
    }
  }'
The legacy sandbox-only assertion signature sandbox-valid-passkey-signature is still accepted for compatibility, but it skips WebAuthn verification and should not be used for production-shaped sandbox tests.

OAuth (OIDC) token

OAuth does not use a fixed magic token in sandbox. Pass a JWT-shaped OIDC token as oidcToken. The JWT signature segment can be a dummy value, but the payload must look like a real ID token. For POST /auth/credentials with type: "OAUTH", the sandbox token must include:
  • iss: a supported issuer, such as https://accounts.google.com, accounts.google.com, or https://appleid.apple.com
  • aud: a non-empty string, or a single-element string array
  • sub: a non-empty subject identifier for the user
  • iat: a numeric issued-at timestamp no more than 60 seconds before the request, with 5 seconds of clock skew allowed
  • exp: a numeric expiration timestamp later than the request time
Grid stores the OAuth credential’s registered identity from iss, aud, and sub. On POST /auth/credentials/{id}/verify, the fresh oidcToken must carry the same iss, aud, and sub as the credential being verified. It must also include nonce equal to sha256(clientPublicKey), where clientPublicKey is the exact hex public key sent in the verify request.
export PUBLIC_KEY="04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"
OIDC_TOKEN=$(node - <<'NODE'
const crypto = require("crypto");

const publicKey = process.env.PUBLIC_KEY || "04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2";
const now = Math.floor(Date.now() / 1000);
const b64url = (value) =>
  Buffer.from(JSON.stringify(value)).toString("base64url");

const payload = {
  iss: "https://accounts.google.com",
  sub: "sandbox-user-123",
  aud: "grid-sandbox-oauth-client-id",
  iat: now,
  exp: now + 300,
  nonce: crypto.createHash("sha256").update(publicKey).digest("hex"),
  email: "sandbox-user-123@example.com",
  email_verified: true
};

console.log(
  `${b64url({ alg: "RS256", typ: "JWT" })}.${b64url(payload)}.sandbox-signature`
);
NODE
)

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": "'"$OIDC_TOKEN"'",
    "clientPublicKey": "'"$PUBLIC_KEY"'"
  }'
The old literal sandbox-valid-oidc-token is no longer accepted. Use a freshly generated sandbox JWT for both OAuth credential registration and OAuth verification. Production requires a real ID token from your provider and verifies the provider signature.

Wallet signature header

After verifying an auth credential, decrypt encryptedSessionSigningKey with the private key matching the clientPublicKey you supplied on verify or refresh. Use the decrypted session signing key to build a Turnkey API-key stamp over the exact payloadToSign string returned by Grid, then pass that full stamp as the Grid-Wallet-Signature HTTP header on signed flows:
  • 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)
  • PATCH /internal-accounts/{id} (update wallet privacy)
  • POST /quotes/{quoteId}/execute (when source is an embedded wallet)
This example uses the sample signer in the Grid API repo’s scripts directory. See the scripts README for setup, or replace SIGN with your own Turnkey API-key stamp implementation.
SIGN="node $(pwd)/scripts/embedded-wallet-sign.js"
STAMP=$($SIGN stamp "$SESSION_PRIV_HEX" "$PAYLOAD_TO_SIGN")

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: $STAMP"
Sandbox validates that the stamp is a P-256 Turnkey API-key stamp over the exact pending Turnkey payload and that the public key belongs to an active sandbox session for the wallet.
The legacy sandbox-only Grid-Wallet-Signature: sandbox-valid-signature value is still accepted for compatibility. Use a real session stamp when you want the client implementation to match production.

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 sandbox magic values and unsigned sandbox OIDC tokens from your client and server code — production runs the real OTP, HPKE, WebAuthn, OIDC signature, and ECDSA flows.
  3. Configure production webhook endpoints.
  4. Test with small amounts first.

Next steps