Node.js Tabs
An agent uses a session key that the wallet owner authorizes for one seller under a cap and expiry. The agent keeps that secret; the owner approves its public key through Dexter's hosted consent page.
Use Node.js 22 or newer with @dexterai/x402@6.0.0 and @dexterai/vault@0.43.4. The client signs context-bound V2 vouchers. Before using a grant, provide reserveFinalVoucherV2: your authenticated payment backend must reserve the exact signed amount and return its confirmed transaction receipt.
Prepare the Callback
You need the seller's settlement address on Solana, a session secret held by your agent, and an HTTPS callback endpoint you control. The callback receives a browser-originated JSON POST. Allow the
https://dexter.cash origin and Content-Type header in its CORS preflight.
Correlate each approval with an unpredictable, single-use requestId stored on your server. The callback contains no server authentication signature. Treat its contents as untrusted until you compare them with the pending request and the on-chain session.
Callback delivery is best-effort. Keep the pending request and session secret so a missing callback can be reconciled with the owner and chain state.
Build an Approval URL
This Node 22 example uses @dexterai/vault@0.43.4 and creates a session key locally. It writes the pending request and secret with owner-only file permissions, refusing to overwrite an existing file.
The output is an approval URL proposing a 5 USDC cap and a one-hour expiry. Generating it makes no on-chain change. Opening it lets the owner review the seller and approve or reduce the proposed authority. The hosted flow handles registration sponsorship subject to its eligibility checks.
Use /tabs/new?req=... for an encoded grant request. /tabs/connect?url=...&agent=... is a separate page that looks up an API's terms and builds a request from them.
Read the Approval Result
The POST body has flat fields. registerTx is present for a new registration; reusing an existing session can return reused: true without it.
ApprovedSpendGrantParams contains:
| Field | Meaning |
|---|---|
counterparty | Seller settlement address. |
sessionPubkey | Public key authorized to sign vouchers. |
maxAmountAtomic | Approved lifetime cap, in atomic USDC. |
expiresAtUnix | Approved expiry, in Unix seconds. |
nonce | Session registration nonce, preserved exactly as returned. |
maxRevolvingCapacityAtomic | Approved outstanding-exposure cap. |
Before accepting a callback, validate its shape and match requestId to a pending request. Require the expected session public key and seller; reject a raised cap or extended expiry. Read the session under vaultPda and compare its stored authority and limits. A returned TypeScript type or transaction string alone establishes none of those checks.
Use the POST callback when you need all grant parameters. The redirect callback omits the nonce and revolving-capacity fields needed to reconstruct the grant.
Connect the Approved Grant
After validating the callback, pass its approved parameters unchanged to tabFromGrant. This helper connects the session and returns a payment handle:
Your reservation provider receives the complete signed voucher, its digest, the exact increment, and an idempotency key. Persist that request before submission. Repeated calls with the same identity must recover the same reservation. Return a FinalVoucherV2ReservationReceipt after confirmation; the SDK independently verifies its transaction, voucher-binding Memo, and account state before releasing the voucher.
The callback implements a backend contract, rather than a bundled SDK HTTP endpoint. It requires the reservation authority configured for the Vault. See the reservation receipt contract for its fields. A generic successful HTTP response cannot substitute for that receipt.
Send the resulting voucher with its receipt intact:
Choose the increment from the seller's quote and persist the released voucher before dispatch in your application. If delivery is uncertain, recover that request's reservation and delivery state before authorizing another charge.
Client Lifecycle
Preserve the grant's nonce exactly. Historical V1 buyer grants raise HistoricalV1MigrationRequiredError and need an owner-approved replacement before this client can use them.
| Method | Behavior |
|---|---|
tabFromGrant(...) | Checks the session secret against params.sessionPubkey, reads the on-chain session, and creates a local handle. Requires an RPC connection and a per-voucher cap. |
signNextVoucher(incrementAtomic) | Signs an increase, confirms its exact reservation, then advances local cumulative state before delivery. |
stream(url) | Signs the entire perUnitCap once, then requests an SSE stream. |
close() | Attempts settlement and clears the local key after success. A grant handle does not revoke the owner's session. |
Serialize spending under one session key. The initial on-chain resume point can omit outstanding off-chain vouchers, so retain those vouchers and reconcile them before resuming after a crash. tab.state.spent includes the session's previous cumulative spend, not just the current process's activity.
A reservation failure leaves local counters unchanged so the same signing request can recover the same voucher identity. Preserve the provider journal, voucher, and transaction identifiers before retrying; avoid switching to another payment method while the first outcome is uncertain. The wallet owner controls revocation; closing and revoking have different effects.
Last Updated: