JSPM

x402-seller-sdk

0.2.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 2
  • Score
    100M100P100Q42694F
  • License MIT

JWT proof verification SDK for x402 sellers - stateless payment verification using JWKS

Package Exports

  • x402-seller-sdk
  • x402-seller-sdk/guard

Readme

x402-seller-sdk

JWT proof verification SDK for x402 sellers. Verify payment proofs using JWKS (JSON Web Key Set) with automatic key rotation support.

Installation

npm install x402-seller-sdk

Quick Start (Framework-Agnostic Guard)

The simplest way to add x402 payment verification to any framework. Works with Express, Hono, Koa, Next.js, Cloudflare Workers, and more.

import { createX402 } from 'x402-seller-sdk/guard';

const x402 = createX402({
  jwksUrl: process.env.X402_JWKS_URL!,
  issuer: process.env.X402_EXPECTED_ISS,
  audience: process.env.X402_EXPECTED_AUD,
});

// In your route handler (any framework):
const result = await x402.requirePayment({
  headers: req.headers,
  cookies: req.cookies,
  expectedResource: '/api/today',
  apiKey: { header: 'x-api-key', value: process.env.API_KEY_VALUE },
  debug: process.env.X402_DEBUG === '1',
});

if (!result.ok) {
  // Payment required - send 402 response
  return res.status(result.status).json(result.body);
}

// Payment verified! Use result.claims and result.headers
console.log('Paid by:', result.claims.buyer);

Alternative: Class-Based SDK

If you prefer a class-based API or need more fine-grained control:

import { SellerSDK } from 'x402-seller-sdk';

const sdk = new SellerSDK({
  jwksUrl: process.env.X402_JWKS_URL!,
  issuer: process.env.X402_EXPECTED_ISS,
  audience: process.env.X402_EXPECTED_AUD,
});

// Verify a payment proof
try {
  const proof = await sdk.verifyProof(token, '/api/today');
  console.log('Payment verified:', proof);
} catch (error) {
  console.error('Verification failed:', error.message);
}

Environment Variables

X402_JWKS_URL="https://gateway.x402.org/.well-known/jwks.json"
X402_EXPECTED_ISS="x402-gateway"
X402_EXPECTED_AUD="your-project-id"

Framework Examples (Guard API)

Express

import express from 'express';
import cookieParser from 'cookie-parser';
import { createX402 } from 'x402-seller-sdk/guard';

const x402 = createX402({
  jwksUrl: process.env.X402_JWKS_URL!,
  issuer: process.env.X402_EXPECTED_ISS,
  audience: process.env.X402_EXPECTED_AUD,
});

const app = express();
app.use(cookieParser());

app.get('/api/today', async (req, res) => {
  const result = await x402.requirePayment({
    headers: req.headers as Record<string, string>,
    cookies: req.cookies,
    expectedResource: '/api/today',
    apiKey: { header: 'x-api-key', value: process.env.API_KEY_VALUE },
    debug: process.env.X402_DEBUG === '1',
  });

  if (!result.ok) {
    res.set(result.headers).status(result.status).json(result.body);
    return;
  }

  // Payment verified! Forward headers if needed
  res.set(result.headers).json({ message: 'paid content' });
});

app.listen(3001);

Hono (Node/Cloudflare Workers)

import { Hono } from 'hono';
import { createX402 } from 'x402-seller-sdk/guard';

const x402 = createX402({
  jwksUrl: process.env.X402_JWKS_URL!,
  issuer: process.env.X402_EXPECTED_ISS,
  audience: process.env.X402_EXPECTED_AUD,
});

const app = new Hono();

app.get('/api/today', async (c) => {
  const result = await x402.requirePayment({
    headers: c.req.raw.headers,
    expectedResource: '/api/today',
    apiKey: { header: 'x-api-key', value: process.env.API_KEY_VALUE },
    debug: process.env.X402_DEBUG === '1',
  });

  if (!result.ok) {
    for (const [k, v] of Object.entries(result.headers)) c.header(k, v);
    return c.json(result.body, result.status);
  }

  for (const [k, v] of Object.entries(result.headers)) c.header(k, v);
  return c.json({ message: 'paid content' });
});

export default app;

Next.js Route Handler

// app/api/today/route.ts
import { NextResponse } from 'next/server';
import { cookies, headers } from 'next/headers';
import { createX402 } from 'x402-seller-sdk/guard';

const x402 = createX402({
  jwksUrl: process.env.X402_JWKS_URL!,
  issuer: process.env.X402_EXPECTED_ISS,
  audience: process.env.X402_EXPECTED_AUD,
});

export async function GET() {
  const result = await x402.requirePayment({
    headers: headers() as any,
    cookies: cookies().getAll(), // {name,value}[]
    expectedResource: '/api/today',
    apiKey: { header: 'x-api-key', value: process.env.API_KEY_VALUE },
    debug: process.env.X402_DEBUG === '1',
  });

  if (!result.ok) {
    const res = NextResponse.json(result.body, { status: result.status });
    for (const [k, v] of Object.entries(result.headers)) res.headers.set(k, v);
    return res;
  }

  const res = NextResponse.json({ message: 'paid content' });
  for (const [k, v] of Object.entries(result.headers)) res.headers.set(k, v);
  return res;
}

Koa

import Koa from 'koa';
import Router from '@koa/router';
import { createX402 } from 'x402-seller-sdk/guard';

const x402 = createX402({
  jwksUrl: process.env.X402_JWKS_URL!,
  issuer: process.env.X402_EXPECTED_ISS,
  audience: process.env.X402_EXPECTED_AUD,
});

const router = new Router();

router.get('/api/today', async (ctx) => {
  const result = await x402.requirePayment({
    headers: ctx.request.headers,
    expectedResource: '/api/today',
    apiKey: { value: process.env.API_KEY_VALUE },
    debug: process.env.X402_DEBUG === '1',
  });

  if (!result.ok) {
    ctx.set(result.headers);
    ctx.status = result.status;
    ctx.body = result.body;
    return;
  }

  ctx.set(result.headers);
  ctx.body = { message: 'paid content' };
});

Next.js Middleware Example

import { NextRequest, NextResponse } from 'next/server';
import { SellerSDK, encodeVerifiedPayload } from '@x402/seller-sdk';

const sdk = new SellerSDK({
  jwksUrl: process.env.X402_JWKS_URL!,
  issuer: process.env.X402_EXPECTED_ISS,
  audience: process.env.X402_EXPECTED_AUD,
});

export async function middleware(req: NextRequest) {
  const url = new URL(req.url);

  // Protect specific routes
  if (url.pathname === '/api/today') {
    const token = SellerSDK.extractProof(req.headers, req.cookies);

    if (!token) {
      return new NextResponse(JSON.stringify({ error: 'Payment required' }), {
        status: 402,
        headers: { 'Content-Type': 'application/json' },
      });
    }

    try {
      const verified = await sdk.verifyProof(token, '/api/today');

      // Inject verified payload for downstream use
      const res = NextResponse.next();
      res.headers.set('x-verified-payload', encodeVerifiedPayload(verified));
      return res;
    } catch (e: any) {
      console.error('x402 verify failed:', e?.message || e);
      return new NextResponse(
        JSON.stringify({ error: 'Invalid or expired payment proof' }),
        { status: 402, headers: { 'Content-Type': 'application/json' } }
      );
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/api/today'],
};

API Reference

Guard API (createX402)

The framework-agnostic guard API (recommended for most use cases).

createX402(options)

Create an x402 payment guard instance.

Options:

  • jwksUrl (required): JWKS endpoint URL
  • issuer (optional): Expected token issuer
  • audience (optional): Expected audience (project ID or array of IDs)
  • clockSkewSec (optional): Clock skew tolerance in seconds (default: 120)

Returns an object with:

requirePayment(args)

One-call guard that extracts, verifies, and returns a ready-to-send response.

Args:

  • headers: Request headers (Headers, Map, or plain object)
  • cookies (optional): Cookies object or array of {name, value}
  • expectedResource (optional): Resource path to validate (e.g., /api/today)
  • apiKey (optional): { header?, value? } - API key to include in response headers
  • debug (optional): Enable debug headers in 402 responses

Returns:

// Success case:
{ ok: true, claims: VerifiedProof, headers: Record<string, string> }

// Failure case (send as-is):
{ ok: false, status: 402, body: { error, reason }, headers: Record<string, string> }
verify(token, expectedResource?)

Low-level token verification (if you want to extract the token yourself).

Returns: VerifiedProof
Throws: Error if verification fails

findProof({ headers, cookies? })

Extract proof token from common header/cookie locations.

Returns: string | null

headersFor(claims, apiKey?)

Build headers to forward to your upstream API.

Returns: Record<string, string> with x-verified-payload and optional API key


Class-Based API (SellerSDK)

Constructor Options

  • jwksUrl (required): JWKS endpoint URL
  • issuer (optional): Expected token issuer
  • audience (optional): Expected audience (project ID)
  • clockSkewSec (optional): Clock skew tolerance in seconds (default: 60)

Methods

verifyProof(token, expectedResource?)

Verify a JWT payment proof.

  • token: The JWT token string
  • expectedResource: Optional resource path to validate
  • Returns: VerifiedProof object with claims
  • Throws: Error if verification fails
SellerSDK.extractProof(headers, cookies?)

Static helper to extract proof token from request headers or cookies.

Verified Proof Claims

{
  resource: string;      // e.g., "/api/today"
  buyer: string;         // buyer identifier
  amount: string;        // payment amount (minor units)
  currency: string;      // e.g., "USDC"
  network: string;       // e.g., "base-sepolia"
  txHash?: string;       // transaction hash (if settled)
  proofId: string;       // unique proof identifier
  exp: number;           // expiration timestamp
  iss: string;           // issuer
  aud: string;           // audience
}

Security Notes

  • Tokens are short-lived (default TTL in gateway)
  • JWKS endpoint supports key rotation via kid (key ID)
  • Clock skew tolerance prevents time sync issues
  • Resource validation prevents token reuse across endpoints

License

MIT