Dexter
Dexter
Docs

Session Server

The session server SDK handles voucher verification, challenge generation, and session context tracking. Sellers integrate it into their API server to accept session-based payments.

Quick Start

import { createSessionServer } from '@dexterai/mpp/server/session';
 
const sessions = createSessionServer({
  recipient: 'YourSolanaWallet...',
  pricePerUnit: '10000', // 0.01 USDC per request
});
 
app.get('/api/data', (req, res) => {
  const voucher = req.headers['x-mpp-voucher'];
 
  // No voucher — return a 402 challenge
  if (!voucher) {
    return res.status(402).json(sessions.getChallenge());
  }
 
  // Verify the voucher
  const result = sessions.verifyVoucher(JSON.parse(voucher));
  if (!result.valid) {
    return res.status(402).json({ error: result.error });
  }
 
  // Voucher valid — serve content
  res.json({ data: 'paid content' });
});

Parameters

ParameterTypeDefaultDescription
recipientstringrequiredSolana wallet address to receive session settlements
pricePerUnitstringrequiredMinimum payment per request in atomic USDC units
apiUrlstringhttps://x402.dexter.cashSettlement API base URL
networkstringmainnet-betaSolana cluster
meterfunctionnoneCustom metering function for variable pricing
suggestedDepositstringnoneSuggested deposit amount returned in challenges

Methods

getChallenge()

Returns a 402 challenge object for clients that don't have a voucher. The challenge includes the recipient, price, network, and suggested deposit so the client knows how to open a session.

const challenge = sessions.getChallenge();
// {
//   type: 'session',
//   recipient: 'YourWallet...',
//   pricePerUnit: '10000',
//   network: 'mainnet-beta',
//   suggestedDeposit: '1000000',
// }

verifyVoucher(voucher)

Verifies a signed voucher. Returns { valid: true } or { valid: false, error: string }.

Verification checks:

  • Ed25519 signature is valid
  • Cumulative amount is monotonically increasing
  • Sequence number is strictly increasing
  • Amount meets or exceeds pricePerUnit
  • Signer pubkey is consistent across all vouchers in the channel
const result = sessions.verifyVoucher({
  channel_id: 'abc123',
  cumulative_amount: '30000',
  sequence: 3,
  signature: '...',
  signer: '...',
  server_nonce: '...',
});
 
if (!result.valid) {
  console.log(result.error); // e.g. "amount_not_monotonic"
}

getSessionContext(channelId)

Returns the current session state for a channel, including cumulative amount and voucher count. Returns undefined if no vouchers have been verified for the channel.

const ctx = sessions.getSessionContext('abc123');
// { channelId: 'abc123', cumulativeAmount: '30000', voucherCount: 3, lastSigner: '...' }

removeSession(channelId)

Removes a session from the server's tracking state. Call this when notified that a session has closed.

Custom Metering

For variable pricing (per-token, per-byte, per-record), provide a meter function:

const sessions = createSessionServer({
  recipient: 'YourWallet...',
  pricePerUnit: '1000', // base price
  meter: (voucher) => {
    // Return the minimum acceptable cumulative amount
    // based on your own usage tracking
    return calculateUsageCost(voucher.channel_id);
  },
});

Express Integration

Full Express example with error handling:

import express from 'express';
import { createSessionServer } from '@dexterai/mpp/server/session';
 
const app = express();
const sessions = createSessionServer({
  recipient: process.env.SOLANA_WALLET,
  pricePerUnit: '10000',
  suggestedDeposit: '1000000',
});
 
app.get('/api/data', (req, res) => {
  const voucherHeader = req.headers['x-mpp-voucher'];
 
  if (!voucherHeader) {
    return res.status(402).json(sessions.getChallenge());
  }
 
  let voucher;
  try {
    voucher = JSON.parse(voucherHeader as string);
  } catch {
    return res.status(400).json({ error: 'invalid voucher format' });
  }
 
  const result = sessions.verifyVoucher(voucher);
  if (!result.valid) {
    return res.status(402).json({ error: result.error });
  }
 
  res.json({ data: 'paid content', channel: voucher.channel_id });
});
 
app.listen(3000);

What Happens at Session Close

When the buyer closes a session, Dexter executes a single TransferChecked instruction from the buyer's Swig wallet to your recipient address for the cumulative amount consumed. You do not need to do anything. The settlement is final once confirmed on-chain.

On this page