traefik-x402

Charge for Traefik routes with x402

A Traefik middleware plugin. It answers 402 Payment Required on the URLs you choose. When the client pays, the plugin verifies and settles the payment through an x402 v2 facilitator, then serves the response.

InstallSource and README

Install

Enable the plugin in the Traefik static configuration, then attach the middleware to a router.

experimental:
  plugins:
    x402:
      moduleName: github.com/lukaszraczylo/traefik-x402
      version: v0.2.1   # use the latest release tag
http:
  middlewares:
    pay:
      plugin:
        x402:
          facilitatorURL: https://x402.org/facilitator
          accepts:
            - network: eip155:84532
              amount: "10000"     # 0.01 USDC in atomic units
              asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
              payTo: "0x209693Bc6afc0C5328bA36FaF03C514EF312287C"
              extra: { name: USDC, version: "2" }
          exact:    [/report]
          prefixes: [/premium/]
          suffixes: [.pdf]

An unpaid request to /premium/data now returns 402 with a PAYMENT-REQUIRED header. The header holds base64 of this JSON, and the body holds the same JSON:

{"x402Version":2,"error":"PAYMENT-SIGNATURE header is required",
 "resource":{"url":"https://api.example.com/premium/data"},
 "accepts":[{"scheme":"exact","network":"eip155:84532","amount":"10000",
   "asset":"0x036CbD53842c5426634e7929541eC2318f3dCF7e",
   "payTo":"0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
   "maxTimeoutSeconds":60,"extra":{"name":"USDC","version":"2"}}]}

The client signs a payment and retries with it in PAYMENT-SIGNATURE. The plugin never touches a wallet or a chain. The facilitator does that.

Choose which URLs to protect

A rule selects a request when the path matches any entry of any list.

FieldMatches when the pathExample
exactequals the entry/report
prefixesstarts with the entry/premium/ matches /premium/a/b
suffixesends with the entry.pdf matches /docs/manual.pdf

Offer several assets for one URL

Each entry in accepts is one payment option. The client picks one. The plugin accepts the payment only when the client's echo equals one of your options exactly. Token values below were read from Base mainnet on 2026-10-03.

accepts:
  - network: eip155:8453            # USDC on Base
    amount: "10000"                 # 0.01 USDC, 6 decimals
    asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
    payTo: "0x209693Bc6afc0C5328bA36FaF03C514EF312287C"
    extra: { name: USD Coin, version: "2" }
  - network: eip155:8453            # EURC on Base
    amount: "10000"                 # 0.01 EURC, 6 decimals
    asset: "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
    payTo: "0x209693Bc6afc0C5328bA36FaF03C514EF312287C"
    extra: { name: EURC, version: "2" }
  - network: solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp
    amount: "10000"
    asset: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
    payTo: "<your Solana address>"

amount is in the token's atomic units, so work it out from decimals(). For an EIP-3009 token, extra carries the EIP-712 domain name and version of the contract. A token without EIP-3009 needs the Permit2 method, which extra.assetTransferMethod selects in the x402 EVM specification. Whether a facilitator offers it is the facilitator's decision. The plugin passes extra through.

Talk to the facilitator

Credentials without a proxy

Values in facilitatorHeaders and in facilitatorAuth can be env:NAME or file:/path references, so secrets stay out of the Traefik configuration. For the Coinbase Developer Platform facilitator, the plugin signs the per-request JWT itself:

facilitatorURL: https://api.cdp.coinbase.com/platform/v2/x402
facilitatorAuth:
  type: cdp
  keyID: env:CDP_API_KEY_ID
  keySecret: file:/run/secrets/cdp_api_key_secret

It signs with EdDSA for an Ed25519 key and ES256 for a P-256 PEM key. One token is cached for each request URI and reused until 30 seconds before it expires.

Check what the facilitator supports

supportedCheckBehaviour
offNo check.
warn (default)Background check after start-up. Logs options the facilitator does not list.
strictChecks before start-up. An unreachable facilitator or an unsupported option stops the plugin. Fills extra.feePayer from the facilitator for options that have none, which Solana needs.

Strict mode ran against https://x402.org/facilitator on 2026-10-03: it accepted Base Sepolia and Solana devnet, filled the Solana fee payer, and rejected Base mainnet, which that facilitator does not list.

Decide who pays

Existing callers can keep working while agents pay. Three options, per rule:

Settle after the upstream answers

settlementOrderUpstream error
after (default)verify, upstream, settleNot charged
beforeverify, settle, upstreamCharged

In after mode the plugin settles when the upstream commits to a status below 400, before the first body byte reaches the client. Bodies stream without buffering. If the facilitator reports a failed settlement, the plugin replaces the response with 402 and a PAYMENT-RESPONSE header that describes the failure. If the settle call itself fails, the plugin answers 500 with unexpected_settle_error.

A replay guard rejects a second request that carries a payment already in use. It closes the gap between verification and settlement. The guard is in memory and belongs to one Traefik instance.

Limits

WebSocket

Traefik runs plugins in Yaegi, which hides Flusher and Hijacker from a wrapped response writer. A request with an Upgrade header to a protected path gets 501 with upgrade_not_supported. Upgrade requests to unprotected paths pass through. Server-sent events work: a request with Accept: text/event-stream settles before the upstream runs.

Measured

Benchmarks ran on an Apple M4 Max (darwin/arm64, Go 1.27.1) with compiled code. Traefik runs the plugin in Yaegi, which is slower. Command: go test -run '^$' -bench . -benchmem -count=3 .

CaseTimeAllocations
Request outside every rule33 ns0
Rule lookup, 9 entries23 ns0
Build a 402 with two options0.80 to 0.83 µs15
Decode a payment header1.6 µs11
Replay guard claim and release50 ns0
Paid request against a loopback facilitator74 to 76 µs223

The paid-request figure includes the in-process facilitator and two loopback HTTP calls. A real facilitator adds its own round trips.

Tests