The rule

Your x402 payment check must execute before any authentication or authorization middleware that returns 401 or 403. If a marketplace crawler hits your endpoint and gets a 401/403 instead of a 402, the crawler treats your endpoint as private and skips indexing it. You miss out on discovery.

This is a property of how catalogs index x402 services. The CDP Bazaar indexer, Pay.sh, Agent Scan, and our own marketplace all follow the same convention: index on the first 402 they observe. No 402, no listing.

Wrong order (you will not be indexed)

app.use(requireBearerToken); // Returns 401 to anonymous traffic app.use(x402PaymentRequired); // Never reached for unauthenticated requests app.post('/api/generate', handler);

A marketplace crawler hits /api/generate, gets 401 from your bearer-token middleware, and walks away. Your endpoint never appears in the index.

Right order (indexed by catalogs)

app.use(x402PaymentRequired); // Returns 402 to anyone without a payment proof app.use(requireBearerToken); // Optional second layer (rare; usually you skip this) app.post('/api/generate', handler);

A crawler hits the endpoint, gets a clean 402 with all the PaymentRequired metadata, indexes you. A paying client hits the endpoint with a valid x402 proof, the gate passes, the handler runs.

TypeScript (Express)

import express from 'express'; import { paymentMiddleware } from 'x402-express'; const app = express(); // CORRECT: x402 first app.use(paymentMiddleware({ payTo: '0x0D78DB61de28417AE50a96234DAA21eAB9f7880a', network: 'base-sepolia', asset: 'USDC', routes: { '/api/generate': { amount: '50000' } // 0.05 USDC = 50000 atomic units }, facilitator: 'cdp', })); // If you genuinely need internal-only auth on top, put it AFTER x402: // app.use('/internal', requireBearerToken); app.post('/api/generate', (req, res) => { res.json({ result: '...' }); }); app.listen(3000);

Go (Gin)

package main import ( "github.com/gin-gonic/gin" "github.com/coinbase/x402/go/pkg/x402gin" ) func main() { r := gin.Default() // CORRECT: x402 first r.Use(x402gin.PaymentRequired(x402gin.Config{ PayTo: "0x0D78DB61de28417AE50a96234DAA21eAB9f7880a", Network: "base-sepolia", Asset: "USDC", Amount: "50000", Facilitator: "cdp", })) // If you need bearer auth on top, register it AFTER x402. // r.Use(requireBearerToken) r.POST("/api/generate", func(c *gin.Context) { c.JSON(200, gin.H{"result": "..."}) }) r.Run(":3000") }

Python (FastAPI)

from fastapi import FastAPI from x402_fastapi import PaymentRequiredMiddleware app = FastAPI() # CORRECT: x402 added first, so it runs first app.add_middleware( PaymentRequiredMiddleware, pay_to="0x0D78DB61de28417AE50a96234DAA21eAB9f7880a", network="base-sepolia", asset="USDC", amount="50000", facilitator="cdp", routes={"/api/generate": {"amount": "50000"}}, ) # Any HTTPBearer / API key dependency should be a per-route dependency, # NOT a middleware that returns 401 unconditionally. @app.post("/api/generate") def generate(): return {"result": "..."}

In FastAPI, middleware order is reversed: the LAST one added runs FIRST. app.add_middleware(PaymentRequiredMiddleware) at the top means it runs first.

Testing it

The fastest way to confirm your order is correct: hit your endpoint anonymously with curl. You should see a 402 with a JSON body that includes x402Version, accepts[], and resource.

$ curl -i https://your-endpoint.example.com/api/generate HTTP/1.1 402 Payment Required content-type: application/json { "x402Version": 2, "resource": { "serviceName": "...", "category": "Data", "...": "..." }, "accepts": [ { "network": "base-sepolia", "asset": "USDC", "amount": "50000", "...": "..." } ], "extensions": { "bazaar": { "info": { "input": {}, "output": {} } } } }

If you see 401 or 403 instead, your auth middleware is running first. Move x402 ahead of it.

The agent registration wizard at /agents/new includes a Test 402 response button on Step 4 that runs this check against the URL you entered and reports each missing field.

Why this matters

  • CDP Bazaar indexes endpoints on the first paid transaction it observes through the CDP facilitator. The 402 response body is the source of truth for the listing metadata.
  • Pay.sh, Agent Scan, and other auto-crawlers do the same. They look for a clean 402, parse the body, and add the endpoint to their catalog.
  • Our own marketplace at /marketplace follows the same convention. Listings published via the wizard get full metadata; listings auto-indexed from third-party catalogs get whatever the catalog stored.