A Cloudflare Worker that acts as a transparent proxy with payment-gated access using the x402 protocol and stateless cookie-based authentication.
Live Demo - Try the built-in endpoints (other routes will fail as no origin is configured):
- /__x402/health - Public health check (200 OK)
- /__x402/protected - Protected endpoint (402 Payment Required)
This template implements a smart proxy that:
- Forwards all requests to the origin server by default
- Intercepts protected routes based on configurable patterns
- Requires payment via the x402 protocol for protected routes
- Issues JWT cookies valid for 1 hour after payment
- Allows access to protected routes without additional payments during the valid period
Note: This template is configured for Base Sepolia testnet. For production, update
NETWORKinwrangler.jsoncto a mainnet network (e.g.,"base").
This proxy is ideal for:
- API Monetization - Charge per API request or time-based access
- Premium Content - Paywall specific routes without modifying your backend
- Rate Limiting with Payments - Convert rate limits into paid tiers
- Microservice Access Control - Add payment gates to existing services
- Demo/Testing Payment Flows - Prototype payment-gated services quickly
- 🔄 Transparent Proxy - Forwards all non-protected requests unchanged
- 🎯 Pattern-Based Protection - Configure which routes require payment
- 🔐 Stateless Authentication - JWT cookies with HMAC-SHA256 signatures
- 💰 x402 Protocol Integration - Accept crypto payments for access
- 🍪 Cookie-Based Sessions - No server-side storage required
- ⚡ Edge Computing - Runs on Cloudflare Workers at the edge
- 🔒 Secure - HttpOnly, Secure, SameSite cookies
- 📦 Lightweight - Minimal overhead, custom JWT implementation (~2-3 KB)
┌─────────────┐
│ Client │
└──────┬──────┘
│
▼
┌──────────────────────────────────────────┐
│ Cloudflare Worker (x402 Proxy) │
│ │
│ ┌────────────────────────────────┐ │
│ │ Pattern Matcher │ │
│ │ Is path protected? │ │
│ └────────┬──────────────┬────────┘ │
│ │ NO │ YES │
│ ▼ ▼ │
│ ┌────────────┐ ┌──────────────────┐ │
│ │ Pass │ │ Auth Middleware │ │
│ │ Through │ │ • Check cookie │ │
│ └────────────┘ │ • Verify payment │ │
│ │ • Issue cookie │ │
│ └──────────────────┘ │
│ │ │
└───────────────────────────┼─────────────┘
▼
┌──────────────────┐
│ Origin Server │
│ (Your Backend) │
└──────────────────┘
Get up and running in under 2 minutes:
# Install dependencies
npm install
# Configure JWT secret for local development
cp .dev.vars.example .dev.vars
node -e "console.log('JWT_SECRET=' + require('crypto').randomBytes(32).toString('hex'))" >> .dev.vars
# Start the dev server
npm run devVisit http://localhost:8787 to see the proxy in action.
- Try
http://localhost:8787/__x402/healthfor a public endpoint - Try
http://localhost:8787/__x402/protectedto see payment requirements
Already ran Quick Start above? Skip to How It Works.
- Node.js 18+
- npm or yarn
- Cloudflare account (for deployment)
- A wallet address to receive payments (see Getting a Wallet Address below)
- Testnet tokens for testing payments (get from CDP Faucet)
You need a wallet address (PAY_TO) to receive payments. Any Ethereum-compatible wallet works—use an existing wallet (MetaMask, Coinbase Wallet, etc.) or create one programmatically with Coinbase Developer Platform.
npm installThe proxy is configured via environment variables in wrangler.jsonc:
| Variable | Required | Description | Example |
|---|---|---|---|
PAY_TO |
Yes | Wallet address to receive payments | "0x..." |
NETWORK |
Yes | Blockchain network for payments | "base-sepolia" or "base" |
JWT_SECRET |
Yes | Secret for signing auth tokens (set as secret) | (64 hex chars) |
PROTECTED_PATTERNS |
Yes | Array of route pricing configurations | See below |
ORIGIN_URL |
No | External URL to proxy to (if not using DNS) | "https://api.example.com" |
ORIGIN_SERVICE |
No | Service Binding to origin Worker | Configured in wrangler.jsonc |
FACILITATOR_URL |
No | x402 facilitator endpoint (defaults to CDP) | "https://x402.org/facilitator" |
Each entry defines a protected route and its payment requirements:
The proxy supports three modes for routing requests to your backend. Choose based on your architecture:
Best for: Traditional backend servers (VMs, containers, other hosting providers)
When ORIGIN_URL is not set, requests are forwarded to the origin server defined in your Cloudflare DNS records.
Setup:
-
Add a DNS record in Cloudflare pointing to your origin server:
- Type:
A(for IP address) orCNAME(for hostname) - Name:
api(or your subdomain) - Content: Your origin server IP or hostname
- Proxy status: Proxied (orange cloud)
- Type:
-
Configure a route in
wrangler.jsonc:"routes": [ { "pattern": "api.example.com/*", "zone_name": "example.com" } ]
-
Deploy. The proxy will forward requests to your origin server automatically.
User → api.example.com → x402 Proxy → Origin Server (via DNS)
Best for: Another Cloudflare Worker, or any external service with a public URL
When ORIGIN_URL is set, requests are rewritten to that URL. This lets you proxy to another Worker on a Custom Domain or any external API.
Setup:
-
Set
ORIGIN_URLinwrangler.jsonc:"vars": { "ORIGIN_URL": "https://my-origin-worker.example.com", // ... other vars }
-
If your origin is a Worker, deploy it with a Custom Domain.
-
Deploy the proxy. Requests are rewritten to the origin URL while preserving the original
Hostheader.
User → api.example.com → x402 Proxy → my-origin-worker.example.com (URL rewrite)
Why External Origin mode? Cloudflare routes a hostname to one Worker only. You can't chain Workers on the same hostname via routing. External Origin mode solves this by rewriting the URL to a different hostname where your origin Worker lives.
Best for: Another Cloudflare Worker in your account (fastest option)
When ORIGIN_SERVICE is bound, requests are sent directly to the bound Worker with zero network overhead. Both Workers run on the same thread.
Setup:
-
Add a service binding in
wrangler.jsonc:"services": [ { "binding": "ORIGIN_SERVICE", "service": "my-origin-worker" } ]
-
Deploy. The proxy will call the origin Worker directly via the binding.
User → api.example.com → x402 Proxy → Origin Worker (via Service Binding)
Why Service Binding mode? Service Bindings provide the fastest Worker-to-Worker communication with no network hop. The origin Worker doesn't even need a public route.
| Mode | Config | Origin Type | Use Case |
|---|---|---|---|
| DNS-Based | (default) | Traditional server | Your backend is a VM, container, or external host |
| External Origin | ORIGIN_URL |
Worker or any URL | Your backend is another Worker or external API |
| Service Binding | ORIGIN_SERVICE |
Worker (same account) | Fastest option for Worker-to-Worker |
-
Copy the example environment file:
cp .dev.vars.example .dev.vars
-
Generate a secure JWT secret:
node -e "console.log('JWT_SECRET=' + require('crypto').randomBytes(32).toString('hex'))" >> .dev.vars
-
Verify your
.dev.varsfile contains:JWT_SECRET=<your-generated-secret>
Start the development server:
npm run devThe server will be available at http://localhost:8787
Most commonly used:
| Command | Description |
|---|---|
npm run dev |
Start local development server |
npm run deploy |
Deploy to Cloudflare Workers |
Other scripts:
| Command | Description |
|---|---|
npm run cf-typegen |
Generate TypeScript types from Worker config |
npm run typecheck |
Run TypeScript type checking |
npm run format |
Format code with Prettier |
npm run format:check |
Check code formatting |
npm run lint |
Run all checks (typecheck + format + ESLint) |
npm run lint:fix |
Auto-fix formatting and linting issues |
npm run test:client |
Run automated end-to-end test |
Understanding the core concepts will help you configure and use this proxy effectively.
The proxy uses a custom JWT implementation built on Web Crypto API:
- Signing: HMAC-SHA256 with secret key
- Payload:
{ paid: true, iat, exp } - Validation: Signature verification + expiration check
- Size: ~2-3 KB minified (no dependencies)
- Client requests protected route (e.g.,
/premium) without cookie - Proxy responds with
402 Payment Required+ payment details - Client creates signed payment (e.g., for USDC on Base Sepolia)
- Client retries request with
X-PAYMENTheader - x402 middleware verifies payment via facilitator
- Proxy issues JWT cookie + forwards request to origin
- Subsequent requests use cookie (valid for 1 hour) - no payment needed
- Origin server receives authenticated requests transparently
Key insight: The origin server never knows about the payment logic. It just receives authenticated requests as if the proxy wasn't there.
The worker acts as a transparent proxy that forwards all requests to your origin server, except for routes matching the PROTECTED_PATTERNS configuration.
Any route NOT in PROTECTED_PATTERNS is forwarded directly to the origin:
curl https://your-worker.dev/any-path
# → Proxied directly to origin serverRoutes matching PROTECTED_PATTERNS require payment or a valid authentication cookie:
Without payment or cookie:
- Returns
402 Payment Required - Includes payment requirements in response body
With valid payment:
- Verifies payment via x402 facilitator
- Issues JWT cookie (valid for 1 hour)
- Proxies request to origin server
With valid cookie:
- Validates JWT signature and expiration
- Proxies request to origin immediately (no payment required)
# First request without auth
curl https://your-worker.dev/premium
# → 402 Payment Required
# Request with payment
curl https://your-worker.dev/premium -H "X-PAYMENT: <encoded-payment>"
# → Cookie issued + request proxied to origin
# Subsequent requests with cookie
curl https://your-worker.dev/premium -H "Cookie: auth_token=..."
# → Proxied to origin (no payment needed)Run the automated test client:
PRIVATE_KEY=0x... npm run test:clientThis will:
- Request
/premiumwithout payment (receives 402) - Create and sign a payment with your wallet
- Submit payment and receive premium content
- Extract JWT cookie
- Test cookie authentication (no payment needed)
See TESTING.md for detailed testing instructions.
- Test public endpoint:
curl http://localhost:8787/__x402/health- Request protected endpoint (no payment):
curl -v http://localhost:8787/__x402/protected
# Returns 402 with payment requirements- Request with payment (requires x402 SDK): See test-client.ts for implementation example
Note: Automated testing with
npm run test:clientrequires a funded wallet with testnet tokens. If you're just evaluating the template, the Playwright tests (pnpm test:e2e x402-proxy-templatefrom repo root) cover core functionality without requiring payments.
.
├── src/
│ ├── index.ts # Main application and routes
│ ├── auth.ts # Authentication middleware
│ └── jwt.ts # JWT utilities (sign/verify)
├── public/
│ └── index.html # Static assets
├── test-client.ts # Automated test client
├── wrangler.jsonc # Cloudflare Worker configuration
├── .dev.vars # Local environment variables (gitignored)
├── .prettierrc # Prettier configuration
├── eslint.config.js # ESLint configuration
└── tsconfig.json # TypeScript configuration
The worker uses a single catch-all middleware that:
- Checks path against
PROTECTED_PATTERNS - For unprotected paths: Proxies request immediately to origin
- For protected paths:
- Checks for valid JWT cookie
- If no valid cookie, requires x402 payment
- Issues JWT cookie on successful payment
- Proxies authenticated request to origin
The proxy mode (DNS-based, External Origin, or Service Binding) determines how requests reach your backend. See Proxy Modes for details.
Single route with one price:
"PROTECTED_PATTERNS": [
{
"pattern": "/premium",
"price": "$0.01",
"description": "Access to premium content for 1 hour"
}
]Multiple routes with different prices:
"PROTECTED_PATTERNS": [
{
"pattern": "/premium",
"price": "$0.01",
"description": "Basic premium access"
},
{
"pattern": "/api/pro/*",
"price": "$0.10",
"description": "Pro API access"
},
{
"pattern": "/dashboard",
"price": "$1.00",
"description": "Full dashboard access"
}
]Wildcard patterns:
"PROTECTED_PATTERNS": [
{
"pattern": "/api/private/*",
"price": "$0.05",
"description": "Private API access"
}
]Cookies are configured with security best practices:
HttpOnly: Prevents JavaScript access (XSS protection)Secure: HTTPS only in productionSameSite=Strict: CSRF protection- 1-hour expiration: Limits exposure window
- Secret key stored in environment variables (not in code)
- HMAC-SHA256 cryptographic signing
- Expiration validation on every request
- No sensitive data in payload
- All payments verified through facilitator
- Client cannot forge payment proofs
- Payment amount validation
- Network/token validation
With Bot Management for Enterprise enabled on your domain, x402-proxy can distinguish between human and automated traffic:
- Humans can access protected routes without payment
- Bots are charged unless explicitly exempted
- You can allow specific crawlers (e.g., Googlebot, search engines) free access
"PROTECTED_PATTERNS": [
{
"pattern": "/api/premium/*",
"price": "$0.10",
"description": "Premium API access",
"bot_score_threshold": 30, // Lower score = more likely automated
"except_detection_ids": [
120623194, // Googlebot
132995013 // ChatGPT-User
]
}
]The configuration uses two settings:
bot_score_threshold(1-99) - determines the cutoff for blocking bot traffic and allowing humans through. See Bot Score for how scores are calculated.except_detection_ids- array of bot detection IDs to whitelist. A sample list is available insrc/bots.ts.
Without Bot Management, all traffic to protected routes requires payment.
Important: For production, update
NETWORKinwrangler.jsoncto a mainnet network (e.g.,"base").
- Set up secrets:
wrangler secret put JWT_SECRET
# Enter your production JWT secret-
Update configuration: Edit
wrangler.jsoncwith your production wallet address and mainnet network -
Deploy:
npm run deployFor multiple environments (dev/staging/prod), use Wrangler environments.
The project enforces code quality through:
- TypeScript - Full type safety
- ESLint - Code quality rules
- Prettier - Consistent formatting
- Pre-commit checks - All checks run via
npm run lint
Simply add a new entry to PROTECTED_PATTERNS in wrangler.jsonc:
{
"vars": {
"PROTECTED_PATTERNS": [
{
"pattern": "/premium",
"price": "$0.01",
"description": "Premium content access",
},
{
"pattern": "/api/private/*",
"price": "$0.05",
"description": "Private API access",
},
{
"pattern": "/dashboard",
"price": "$0.10",
"description": "Dashboard access",
},
],
},
}That's it! No code changes needed. The proxy will automatically:
- Require payment for routes matching any pattern
- Apply the correct price for each route
- Issue cookies after payment
- Forward authenticated requests to your origin server
- Check wallet has enough testnet tokens
- Verify you're on Base Sepolia network
- Ensure payment amount matches requirement
- Check cookie isn't expired (1 hour validity)
- Verify JWT_SECRET is set in
.dev.vars - Ensure cookie is being sent in request headers
- Run
npm run cf-typegento regenerate types after changingwrangler.jsonc - Check
tsconfig.jsonincludes correct files
- x402 Protocol Documentation
- x402 GitHub - Open source repository
- CDP Server Wallet Quickstart - Create wallets programmatically
- Cloudflare Workers Documentation
- Service Bindings - Worker-to-Worker communication
- Base Sepolia Testnet
This project is provided as-is for educational and demonstration purposes.
This is a demonstration project. For questions or issues, please refer to the x402 Discord.