Tabs for Sellers
A Tab lets a buyer authorize cumulative usage with signed vouchers. The seller checks that authorization, meters delivery against it, and settles the accrued amount separately.
Accept a Tab Request
Use Node.js 22 or newer with @dexterai/x402@6.0.0 and @dexterai/vault@0.43.4. The middleware verifies V2 signatures and independently checks the exact confirmed reservation before your handler runs.
This server serves one JSON event for 0.002 USDC. Use a persistent ledger directory and one server process. Set TAB_CHANNEL_ID_CUTOVER to legacy-case-aliases-migrated-or-empty only after proving that directory is empty or completing the ledger migration.
Run node tab-server.mjs. An unpaid request to http://127.0.0.1:3000/quote returns a 402 challenge. A valid reserved voucher authorizing 0.002 USDC returns an SSE event with observedAt, followed by the stream's end event. Publish the endpoint behind HTTPS for external buyers.
Price a Request
The seller advertises perUnit in the payment challenge. With the published streaming client, the buyer signs a fixed increment before each HTTP request and sends it in X-Tab-Voucher. The seller's meter charges against that accepted request budget.
For example, a request budget of 0.001 USDC covers 100 units priced at 0.00001 USDC each. await meter.charge() commits a unit to the ledger before delivery. It cannot ask the buyer for another voucher during the stream. Once the budget is exhausted, the seller stops delivery; another request needs more signed authorization.
Keep the advertised unit price and the handler's meter price consistent. Validate the request and agree any variable price before the buyer signs.
Seller Interfaces
Import these helpers from @dexterai/x402/tab/seller:
| Helper | Purpose |
|---|---|
tabChallengeMiddleware | Respond to an unpaid request with the Tab offer. |
tabMiddleware | Verify the voucher and session, then attach the seller's Tab handle. Accepts a ledger. |
tabOrExactMiddleware | Advertise Tab and per-request payments together; accepts the same ledger and safety-mode configuration. |
requireTab(req) | Read the Tab handle after middleware verification. |
openSse(res, { tab, perUnit }) | Create a meter for an SSE response. |
The SSE meter follows this order for each unit:
await meter.charge()commits the unit to the durable ledger against the request's remaining budget.meter.send(data)writes it to the stream.await meter.end()closes the response when the work finishes.
meter.charge() throws if that unit exceeds the accepted budget. Catch delivery errors and end the meter so it can release the channel lease. Calling tab.charge() directly requires a voucher exchange transport that this release does not provide.
The signed voucher can authorize more than the seller ultimately delivers. Preserve both amounts when a stream fails. The buyer's close operation submits its latest signed cumulative amount for settlement, so delivery failure needs application-level reconciliation.
Preserve Channel State
Set ledgerSafetyMode explicitly. production-single-instance requires restart-safe storage; production-multi-instance also requires atomic cross-process leases and fenced writes. An in-memory ledger is permitted in development mode. The package exports FileChannelLedger and RedisChannelLedger.
Retain the latest signed voucher, accepted cumulative amount, delivered cumulative amount, and settlement references. A request's available budget is its accepted cumulative amount minus the prior delivery total. Losing the delivery total can let a later request consume authorization already used.
Each successful charge() persists its amount before send(). A process crash can leave a recorded charge whose data never reached the buyer. Reconcile that uncertain delivery from your request log; the ledger intentionally retains the charge so a restart cannot reuse the same budget.
Each channel has one renewable delivery lease with an owner token and increasing fence. Stale owners cannot mutate the ledger after takeover. File storage supports one process. With multiple server instances, use RedisChannelLedger or a custom adapter that implements the required durability and fencing contracts. Complete the documented alias and writer cutovers before enabling the production safety mode.
Confirm Settlement
The seller handle exposes cumulative() for accepted authorization and deliveredCumulative() for metered delivery. Settlement requires a successful transaction and recipient transfer. The handle has no settlement callback.
V2 admission has already verified the voucher's reservation. The SDK may also attempt to crystallize accrued usage into an on-chain claim. Track that claim separately: a failed lock does not invalidate the reservation already verified at admission.
Track Settlement covers claim states, lock cadence, and recovery after a stream ends.
Last Updated: