This page covers generating the client key pair, sending the public key to your backend, holding 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
The key pair you generate here is your session key: you keep the private key on the device and use it directly to sign account actions. Grid never transmits a session key, so session-issuing responses carry no key material. Generate it as a P-256 signing key (ECDSA). Generate a fresh key pair for every authentication and session refresh. Where the public key goes depends on the flow:PASSKEY: send it asclientPublicKeyonPOST /auth/credentials/{id}/challenge.OAUTH: send it asclientPublicKeyonPOST /auth/credentials/{id}/verify.EMAIL_OTP: put it insideencryptedOtpBundleonPOST /auth/credentials/{id}/verify. See Encrypt the OTP code.- Session refresh: send it as
clientPublicKeyon both/auth/sessions/{id}/refreshcalls.
IndexedDB encrypted under a non-extractable Web Crypto key). Send the public key through your integrator backend as a compressed SEC1 point in lowercase hex: 66 characters, a 02 or 03 prefix followed by the 32-byte X coordinate. Grid rejects an uppercase or uncompressed key with 400 INVALID_INPUT.
The private key must not leave the device. Your integrator backend only ever sees
publicKeyHex.Wallet export key pair
Wallet export encrypts the wallet credentials to the key you send, so it needs a P-256 key agreement (ECDH) key pair rather than a signing key. Generate a fresh one for each export and discard the private key after decrypting. Send its public key asclientPublicKey on both /export calls, as an uncompressed SEC1 point in lowercase hex: 130 characters, a 04 prefix followed by the 32-byte X and 32-byte Y coordinates. The samples below produce that form. See Exporting a wallet for the full flow.
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 public key, 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.
public_key must be the compressed public key in lowercase hex: 66 characters starting with 02 or 03. The step 1 samples produce this form. The OTP login challenge is bound to this exact key, so the same compressed hex must be the publicKey in the Grid-Wallet-Signature stamp on the signed retry (see step 2); a different value there fails the login.
In production, an uncompressed key makes the enclave reject the OTP code, even when the code is correct. The first verify call then returns:
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).
2. 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/{id}/verify(EMAIL_OTPorSMS_OTP) — 202 response body; use the TEK private key generated for this login.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.
payloadToSign string as returned (do not re-parse, re-serialize, or trim whitespace). For OTP verification, use the matching TEK private key; for other signed wallet actions, use an active session signing key. Build a Grid wallet signature: take its compressed P-256 public key in lowercase hex, sign the payload with the private scalar, then base64url-encode the signature envelope:
Grid-Wallet-Signature header on the retry (and, for endpoints that use it, echo the 202 requestId as Request-Id).
Sign with real stamps in sandbox too. Sandbox verifies them exactly as production does. OTP verification requires a stamp over the returned
payloadToSign with the TEK private key. Deprecated: the placeholder Grid-Wallet-Signature: sandbox-valid-signature, which some other signed wallet actions still accept in sandbox in place of a real stamp. It will be removed, so send a real stamp on every signed action.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.