Generate the P-256 client key pair, decrypt the session signing key, and sign wallet actions on Web, iOS, and Android
Every signed Embedded Wallet action uses two key pairs:
Key pair
Where it lives
What it does
Client key pair (P-256)
On the customer’s device, generated fresh per verification request
Used as the HPKE recipient key so Grid can encrypt the session signing key to the client. Ephemeral — one pair per POST /auth/credentials/{id}/verify call.
Session signing key (P-256)
Issued by Grid, encrypted to the client public key, decrypted and held on the device
Signs every wallet action for the lifetime of the session (default 15 minutes).
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.
Generate a fresh P-256 key pair for every POST /auth/credentials/{id}/verify call and for every wallet export. 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 — to your integrator backend, which passes it to Grid as clientPublicKey. The Web Crypto, iOS, and Android APIs shown below all produce this format natively.
For local development, you can generate a P-256 key pair from the command line:
2. Verify the credential and receive the encrypted session signing key
Your client sends publicKeyHex 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
The wire format is a base58check string. Decoded, the payload is a 33-byte compressed P-256 encapsulated public key followed by AES-256-GCM ciphertext (ciphertext || 16-byte auth tag).
Grid returns payloadToSign strings from several endpoints:
POST /quotes (when the source is an Embedded Wallet) — the quote’s paymentInstructions[].accountOrWalletInfo.payloadToSign.
POST /auth/credentials (adding an additional credential) — 202 response body.
DELETE /auth/credentials/{id}, DELETE /auth/sessions/{id}, POST /internal-accounts/{id}/export — all 202 response bodies.
Sign the payload byte-for-byte as returned (do not re-parse, re-serialize, or trim whitespace). The signature is ECDSA over SHA-256 using the session signing key, DER-encoded, then base64-encoded. Pass it as the Grid-Wallet-Signature header on the retry (and, for endpoints that use it, the Request-Id header echoed back from the 202).
// npm i @noble/curves @noble/hashesimport { p256 } from "@noble/curves/p256";import { sha256 } from "@noble/hashes/sha256";function bytesToBase64(bytes: Uint8Array): string { let binary = ""; for (const byte of bytes) { binary += String.fromCharCode(byte); } return btoa(binary);}function signPayload( sessionPrivateKeyBytes: Uint8Array, // 32 bytes, from decryptSessionSigningKey payloadToSign: string,): string { const digest = sha256(new TextEncoder().encode(payloadToSign)); const signature = p256.sign(digest, sessionPrivateKeyBytes); return bytesToBase64(signature.toDERRawBytes());}
Your backend adds the signature to the retry request:
Sessions are valid for 15 minutes by default. The AuthSession.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.
If the device is lost or compromised, the user should add a second credential from a trusted device and revoke the compromised one — see Managing credentials. To end the current browser or app session without touching credentials, see Sessions.