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:Testing Outgoing Payments
To test sending payments from your platform, follow these steps:- Look up a sandbox UMA address:
- Create a quote as normal:
- Instead of making a real bank transfer, use the sandbox send endpoint with the quote ID:
Testing Incoming Payments
To test receiving payments to your platform’s users, use the sandbox receive endpoint:- You’ll receive an
INCOMING_PAYMENTwebhook withstatus: "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 customer:
umaAddress, which you can use as the receiverUmaAddress below.
- Test receiving a payment:
- Test sending a payment:
Testing Error Scenarios
You can test various error scenarios using the special sandbox UMA addresses:- Test compliance failures:
- Test long-pending payments:
- Non-existent UMA address:
Global Account magic values
The Grid sandbox lets you exercise Global Account auth flows without moving real money. Email OTP and SMS OTP use the fixed sandbox code000000 — HPKE-encrypt that code in the encryptedOtpBundle just like production. Passkey auth can use the same browser WebAuthn ceremony as production, and signed wallet actions can use the same 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 runs real HPKE end-to-end for EMAIL_OTP and SMS_OTP: clients build a real encryptedOtpBundle against the sandbox otpEncryptionTargetBundle and stamp the exact returned payloadToSign string with their TEK keypair. The only sandbox shortcut is the magic OTP code the user “receives” instead of a real email or SMS delivery.
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 and SMS OTP code
HPKE-encrypt the code000000 (together with your TEK public key) inside encryptedOtpBundle. The sandbox skips email and SMS delivery but runs real HPKE decryption and signature verification.
See Encrypt the OTP code for how to build the bundle. The flow is the same for both EMAIL_OTP and SMS_OTP:
- Call
POST /auth/credentials/{id}/challengeto getotpEncryptionTargetBundle - Generate a TEK key pair and HPKE-encrypt
{otp_code: "000000", public_key: tekPublicKeyHex}. Use the compressed public key (02or03prefix) so the same code works in production. - Submit
encryptedOtpBundletoPOST /auth/credentials/{id}/verify - Receive
202withpayloadToSignandrequestId - Sign
payloadToSignwith the TEK private key and retry withGrid-Wallet-Signature+Request-Idheaders
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 compressed P-256 clientPublicKey (lowercase hex) whose private key becomes the session signing key.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.clientPublicKey is the session signing key.
OAuth (OIDC) token
OAuth does not use a fixed magic token in sandbox. Pass a JWT-shaped OIDC token asoidcToken. 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 ashttps://accounts.google.com,accounts.google.com, orhttps://appleid.apple.comaud: a non-empty string, or a single-element string arraysub: a non-empty subject identifier for the useriat: a numeric issued-at timestamp no more than 60 seconds before the request, with 5 seconds of clock skew allowedexp: a numeric expiration timestamp later than the request time
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 the lowercase hex SHA-256 digest of clientPublicKey, hashed as the exact string sent in the verify request. That string is the compressed public key in lowercase hex: 66 characters starting with 02 or 03.
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
ForPASSKEY and OAUTH credentials and session refresh, the private key matching the clientPublicKey you supplied is the session signing key. For EMAIL_OTP, the TEK private key you generated for the encrypted OTP flow is the session signing key. Use the session signing key to build a Grid wallet signature over the exact payloadToSign string returned by Grid, then pass that full signature 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 Grid wallet-signature implementation.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/sendinstead of real bank transfers - Test UMA Addresses: Special sandbox addresses for testing different scenarios
- Money: No real money is moved in sandbox