This page covers generating the client key pair, sending the public key to your backend, decrypting the session signing key, and signing payloads. Everything here runs on the client; your integrator backend only relays opaque byte strings.
1. Generate a client key pair
Generate a fresh P-256 key pair for every authentication, session refresh, and wallet export. The public key is sent to Grid asclientPublicKey — for PASSKEY credentials this happens on POST /auth/credentials/{id}/challenge; for EMAIL_OTP and OAUTH it happens on POST /auth/credentials/{id}/verify; for session refresh it goes on both /auth/sessions/{id}/refresh calls; for wallet export it goes on both /export calls. Keep the private key in device-local secure storage (browser IndexedDB gated by Web Crypto’s non-extractable flag, iOS Keychain, Android Keystore). Send the public key hex-encoded — a 130-character string starting with 04 — through your integrator backend. The Web Crypto, iOS, and Android APIs shown below all produce this format natively.
The private key must not leave the device. Your integrator backend only ever sees
publicKeyHex.Encrypt the OTP code (EMAIL_OTP only)
EMAIL_OTP credentials never send the OTP code in plaintext. Instead, the client HPKE-encrypts the code (together with its publicKeyHex) to an enclave key, so the code is unreadable in transit and Grid is only a pass-through.
Grid returns an otpEncryptionTargetBundle whenever it initiates or reissues an OTP challenge, including POST /auth/credentials/{id}/challenge and add-EMAIL_OTP signed-retry responses. First-time EMAIL_OTP wallet bootstrap registration can omit it; if the registration response has no bundle, call POST /auth/credentials/{id}/challenge for that credential before verifying. The bundle is a signed enclave bundle whose data field is hex-encoded JSON carrying the enclave’s HPKE target key as targetPublic. Pull out targetPublic, HPKE-encrypt { otp_code, public_key } to it, and submit the library’s { encappedPublic, ciphertext } output as encryptedOtpBundle on POST /auth/credentials/{id}/verify.
Use an HPKE library so you don’t hand-roll the suite, info, or AAD. The
helper below assumes your crypto layer returns the JSON string Grid expects for
encryptedOtpBundle.
Web (TypeScript)
A production client should also verify the bundle’s
dataSignature against its enclaveQuorumPublic before trusting targetPublic.payloadToSign), so EMAIL_OTP responses omit encryptedSessionSigningKey.
2. Verify the credential and receive the encrypted session signing key
Your client sendspublicKeyHex to your integrator backend along with whatever the credential type requires (OTP value, OIDC token, or WebAuthn assertion — see Authentication). Your backend calls POST /auth/credentials/{id}/verify and returns the encryptedSessionSigningKey from Grid’s response to the client.
Grid encrypts the session signing key with HPKE (RFC 9180) using the suite:
- KEM: DHKEM(P-256, HKDF-SHA256)
- KDF: HKDF-SHA256
- AEAD: AES-256-GCM
encappedPublicUncompressed || recipientPublicKeyUncompressed.
Sandbox supports the same decryptable
encryptedSessionSigningKey format for production-shaped PASSKEY and OAUTH tests. The legacy Grid-Wallet-Signature: sandbox-valid-signature shortcut is still accepted, but using real session bundles and stamps catches client-side format bugs before production.3. Decrypt the session signing key
4. Sign a payloadToSign
Grid returns payloadToSign strings from several endpoints:
POST /quotes(when the source is a Global Account) — the quote’spaymentInstructions[].accountOrWalletInfo.payloadToSign.POST /auth/credentials(adding an additional credential) — 202 response body.DELETE /auth/credentials/{id},DELETE /auth/sessions/{id},POST /auth/sessions/{id}/refresh,POST /internal-accounts/{id}/export,PATCH /internal-accounts/{id},PATCH /customers/{id}for tiedEMAIL_OTPemail updates — all 202 response bodies.
Grid-Wallet-Signature header on the retry (and, for endpoints that use it, echo the 202 requestId as Request-Id).
In sandbox, send
Grid-Wallet-Signature: sandbox-valid-signature for any signed account action. Sandbox skips the ECDSA check, so you don’t need a real session signing key or an extracted payloadToSign. The signing pattern below applies only to production.Session lifetime
Sessions are valid for 15 minutes by default. TheAuthSession.expiresAt field tells you exactly when the session signing key stops being accepted. After expiry, the client must re-verify the credential (see Authentication) to obtain a fresh session.