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-sdkQuick 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 URLissuer(optional): Expected token issueraudience(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 headersdebug(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 URLissuer(optional): Expected token issueraudience(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 stringexpectedResource: Optional resource path to validate- Returns:
VerifiedProofobject 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