KYC/KYB verification
In sandbox, you can trigger specific KYC/KYB verification outcomes using magic suffixes in customer and beneficial owner fields. These let you test different verification flows without waiting for real review. OnPOST /verifications, the first submission emits VERIFICATION.IN_PROGRESS and then one VERIFICATION.<STATUS> event for the outcome. A later submission emits one VERIFICATION.<STATUS> event when its status or errors change. The matching CUSTOMER.KYC_* or CUSTOMER.KYB_* event fires when kycStatus or kybStatus changes.
Individual customer verification (KYC)
The last 3 characters of thefullName on POST /customers (with customerType: INDIVIDUAL) decide the customer’s kycStatus. When that outcome applies depends on your platform:
- Unregulated platforms (the default, with Skip verification paperwork off) — the customer is created
UNVERIFIEDand stays there until you submit withPOST /verifications. An incomplete submission returnsRESOLVE_ERRORS; the suffix decides the result once the submission is complete. - Unregulated platforms with Skip verification paperwork turned on in the sandbox dashboard’s Configuration — the suffix resolves the customer at creation, with no documents required. Creation emits the matching
CUSTOMER.KYC_*webhook only; it creates no verification and emits noVERIFICATION.*event.002and any value other than001/003are terminal there.001and003land atPENDINGandUNVERIFIED, which you can still submit for verification — and that submission carries the full data and document requirements below. Before the firstPOST /verifications, aPATCHthat changesfullNamere-derives the status from the new suffix while the customer is not final. - Regulated platforms — the customer is
APPROVEDat creation. The suffix is not read, and Skip verification paperwork does not apply.
Once a customer reaches
APPROVED or REJECTED, that status is final: further POST /verifications calls return 400, and renaming the customer does not re-derive it. Create a new customer when you want to exercise a different outcome.
With Skip verification paperwork on, only 001 and 003 customers can be submitted for verification. Use any non-suffixed name to mint ready-to-transact approved customers, and a 001 name to exercise the full submission flow.
Where a submission is required — the default unregulated path, and 001/003 with the switch on — it carries the same data requirements as production: full name, birth date, nationality, idType + identifier, address, and an identity document uploaded via POST /documents. Anything missing comes back as verificationStatus: RESOLVE_ERRORS with one entry per problem in errors:
001 suffix behaves the same way with Skip verification paperwork on, since 001 and 003 always require a complete packet; a terminal suffix on that path resolves at creation, and POST /verifications then returns 400.
1
Create a customer with a 001 suffix
A
fullName ending in 001 leaves the customer submittable rather than resolving it terminally — UNVERIFIED on an unregulated platform, PENDING with Skip verification paperwork on. Persist the returned id for the following steps.2
Submit for verification
The first attempt returns
verificationStatus: RESOLVE_ERRORS with every missing field and document listed in errors.3
Supply the missing fields and document
Patch the missing fields onto the customer, then upload the identity document. Any JPEG, PNG, or PDF works in sandbox.
4
Resubmit
The verification now advances to
verificationStatus: PENDING_MANUAL_REVIEW and the customer’s kycStatus is PENDING.Business customer verification (KYB)
The last 3 characters ofbusinessInfo.registrationNumber decide the business’s kybStatus. The same suffix applies whether you submit with POST /verifications or the applicant completes the hosted flow. When that outcome applies depends on your platform:
- Regulated platforms — the business is
APPROVEDat creation. The suffix is not read, and Skip verification paperwork does not apply. - Unregulated platforms (the default, with Skip verification paperwork off) — the business is created
UNVERIFIEDand stays there until you submit withPOST /verifications. The packet is validated first. An incomplete submission returnsRESOLVE_ERRORSwhatever the suffix; the suffix decides the result once the submission is complete. - Unregulated platforms with Skip verification paperwork turned on in the sandbox dashboard’s Configuration — the suffix resolves the business at creation. No documents, beneficial owners, or
POST /verificationscall are needed. Creation emits the matchingCUSTOMER.KYB_*webhook only; it creates no verification and emits noVERIFICATION.*event. Before the firstPOST /verifications, aPATCHthat changesregistrationNumberre-derives the status from the new suffix while the business is not final. OnPOST /verifications, the suffix is read before the packet check.
Once a business customer reaches
APPROVED or REJECTED, that status is final: further POST /verifications calls return 400, and changing registrationNumber does not re-derive it. Create a new business customer when you want to exercise a different outcome.
For direct API onboarding with Skip verification paperwork off, every suffix requires complete business information, business documents, and at least one beneficial owner before POST /verifications moves past RESOLVE_ERRORS — an incomplete 002 business reports its missing data before it rejects. With the switch on, POST /verifications reads the suffix before the packet check: a 003 business returns RESOLVE_ERRORS with a POOR_QUALITY_DOCUMENT error with no packet, and a 001 business still needs the complete packet before POST /verifications moves past RESOLVE_ERRORS. For the hosted flow, see Configuring customers.
Beneficial owner KYC
WhenPOST /verifications resolves a business from its registration-number suffix, each of its beneficial owners is resolved at the same moment using the last 3 characters of the lastName in that owner’s personalInfo:
A
001 registration number does not read owner suffixes: the business and every owner go to PENDING. On a 003 business, owners with an approving suffix keep their current status until the business reaches a final outcome. With Skip verification paperwork on, a business with a final suffix resolves at creation before any owner exists, so use a 003 registration number when you want to test owners.
Adding external accounts
The flows for creating external accounts in sandbox are the same as in production. The last 3 digits of an external account’s primary identifier (account number, IBAN, CLABE, Spark wallet address, etc.) determine the test scenario when that account is used in transfers or quotes. For identifiers with a domain part (e.g. PIX email keys), append the test digits to the username portion — for example,testuser.002@pix.com.br.
These outcomes apply whenever the account is used, as the source of a pull or the
destination of a payout.
Beneficiary name verification
For account types that support beneficiary name verification, you can simulate different verification outcomes in sandbox. Use account identifiers with a1xx suffix to trigger verification scenarios (this range is reserved for verification and does not conflict with transfer or quote test patterns):
Funding an internal account
Internal accounts are funded from their payment instructions or by pulling from an external account — see Internal Accounts for both. Sandbox adds a shortcut that skips the wait:immediatelyExecute set to true. The account’s suffix decides the outcome.
Creating quotes
When creating a quote with an external account destination, the account number suffix determines the payment outcome after quote execution:Executing a quote
After creating a quote, you need to fund it to trigger execution. There are two ways to do this in sandbox: Prefunded internal account — If your quote’s source is an internal account, fund the account using one of the methods described in funding an internal account, then call the quote execute endpoint to trigger the transaction:/sandbox/send to simulate this payment:
Sending to a UMA address
For UMA-based payments, use these sandbox addresses to simulate different scenarios:Simulating incoming UMA payments
Use the sandbox receive endpoint to simulate an incoming UMA payment to one of your platform’s users: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 sign a real verificationToken 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} - 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 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.encryptedSessionSigningKey, sealed to the clientPublicKey, just like production.
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 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 sha256(clientPublicKey), where clientPublicKey is the exact hex public key sent in the verify request.
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, decrypt encryptedSessionSigningKey with the private key matching the clientPublicKey you supplied on verify or refresh. For EMAIL_OTP, the TEK private key you generated for the encrypted OTP flow is the session signing key — no decryption step needed. 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.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.