An open API service indexing awesome lists of open source software.

https://github.com/albttx/traefik-plugin-x402

traefik plugin for processing x402 payments
https://github.com/albttx/traefik-plugin-x402

traefik traefik-plugin x402

Last synced: 3 days ago
JSON representation

traefik plugin for processing x402 payments

Awesome Lists containing this project

README

          


traefik-plugin-x402

# traefik-plugin-x402

Traefik v3 middleware that gates HTTP routes behind on-chain stablecoin micropayments using the [x402 protocol](https://x402.org).

[![tests](https://github.com/albttx/traefik-plugin-x402/actions/workflows/test.yml/badge.svg)](https://github.com/albttx/traefik-plugin-x402/actions/workflows/test.yml)
![Go version](https://img.shields.io/badge/go-1.24-blue)
![License](https://img.shields.io/badge/license-MIT-green)

---

## Why

Put any HTTP service behind a paywall without touching its source code. A client that cannot pay gets a standard `402 Payment Required` with a machine-readable `PaymentRequirements` body. A client that can pay signs the payment off-chain, retries the request, and the middleware handles verify-and-settle against a remote facilitator — no custom backend logic required.

The default facilitator is Coinbase's public `https://x402.org/facilitator`, free to use on Base Sepolia. USDC on Base / Base Sepolia is the primary supported asset.

The plugin has **zero non-stdlib runtime dependencies** and is interpreted by [Yaegi](https://github.com/traefik/yaegi) at Traefik startup. It never touches private keys or constructs on-chain transactions — all cryptographic verification and settlement is delegated to the facilitator over plain HTTPS JSON.

---

## How it works

```
Client Traefik (x402 middleware) Facilitator Upstream
| | | |
|--- GET /api ---------------> | | |
|<-- 402 PaymentRequirements --| | |
| | | |
|--- GET /api | | |
| X-PAYMENT: ----> | | |
| |--- POST /verify -----------> | |
| |<-- { isValid: true } ------- | |
| | | |
| |--- forward request ----------------------------->|
| |<-- 200 body (buffered) --------------------------|
| | | |
| |--- POST /settle ------------>| |
| |<-- { success: true } --------| |
| | | |
|<-- 200 body | | |
| X-PAYMENT-RESPONSE: | | |
```

1. **No `X-PAYMENT` header** — middleware returns `402` with a `PaymentRequirements` JSON body. No upstream call is made.
2. **`X-PAYMENT` present** — middleware base64-decodes the header, JSON-parses the `PaymentPayload`, and POSTs to `{facilitatorURL}/verify`.
3. **Verify fails** — middleware returns `402` with the failure reason. No upstream call is made.
4. **Verify succeeds** — middleware forwards the request to the upstream and **buffers the full response body**.
5. **Upstream returns 2xx** — middleware POSTs to `{facilitatorURL}/settle`. On success, adds `X-PAYMENT-RESPONSE` (base64 JSON containing the on-chain tx hash) to the response. If settle fails, the 2xx is replaced with a `402`.
6. **Upstream returns non-2xx** — settle is skipped; the buffered error response is forwarded as-is.

> Streaming responses are not supported — the upstream response is always fully buffered before settle is attempted.

---

## Install

### Option A — Traefik Plugin Catalog (recommended)

> Available once the plugin is listed at https://plugins.traefik.io.

In Traefik's **static configuration**:

```yaml
# traefik.yml
experimental:
plugins:
x402:
moduleName: github.com/albttx/traefik-plugin-x402
version: v0.1.0 # replace with the latest tagged release
```

### Option B — Local plugin (self-hosted / air-gapped)

Mount the plugin source under Traefik's plugin directory at the path matching its module name:

```
/plugins-local/
src/
github.com/
albttx/
traefik-plugin-x402/
plugin.go
go.mod
.traefik.yml
internal/
x402/
```

Then in static config:

```yaml
# traefik.yml
experimental:
localPlugins:
x402:
moduleName: github.com/albttx/traefik-plugin-x402
```

---

## Configure

### Options

| Name | Required | Default | Description |
|---|---|---|---|
| `payTo` | yes | — | Recipient wallet address (EVM checksum address) |
| `network` | yes | — | Network identifier, e.g. `base`, `base-sepolia` |
| `asset` | yes | — | ERC-20 contract address of the payment token |
| `amount` | yes | — | Amount in atomic units as a decimal string. USDC has 6 decimals, so `"1000"` = $0.001 |
| `assetName` | yes¹ | `""` | EIP-712 domain `name` for the asset. For Circle USDC: `USDC`. |
| `assetVersion` | yes¹ | `""` | EIP-712 domain `version` for the asset. For Circle USDC: `2`. |
| `description` | no | `""` | Human-readable description included in the `PaymentRequirements` |
| `mimeType` | no | `application/json` | MIME type of the protected resource |
| `resource` | no | full request URL | Override the resource URL advertised in `PaymentRequirements` |
| `scheme` | no | `exact` | Payment scheme. The public facilitator implements `exact`. |
| `facilitatorURL` | no | `https://x402.org/facilitator` | Base URL of the x402 facilitator |
| `maxTimeoutSeconds` | no | `60` | Maximum time (seconds) the client has to complete the payment |

¹ `assetName` / `assetVersion` are required by any facilitator implementing the `exact` scheme on EVM — they're embedded in `PaymentRequirements.extra` so the client signs against the correct EIP-712 domain. Omitting them produces an `invalid_exact_evm_missing_eip712_domain` verify error. For non-USDC tokens, read the contract's `name()` and check the source for the EIP-712 version constant.

### Docker labels (Compose / Swarm)

```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.paid.rule=Host(`api.example.com`)"
- "traefik.http.routers.paid.middlewares=x402-pay"
- "traefik.http.middlewares.x402-pay.plugin.x402.payTo=0xYourWallet"
- "traefik.http.middlewares.x402-pay.plugin.x402.network=base"
- "traefik.http.middlewares.x402-pay.plugin.x402.asset=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
- "traefik.http.middlewares.x402-pay.plugin.x402.amount=1000"
- "traefik.http.middlewares.x402-pay.plugin.x402.assetName=USDC"
- "traefik.http.middlewares.x402-pay.plugin.x402.assetVersion=2"
```

### File provider (dynamic config)

```yaml
# dynamic.yml
http:
middlewares:
x402-pay:
plugin:
x402:
payTo: "0xYourWallet"
network: "base"
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" # USDC on Base mainnet
amount: "1000"
assetName: "USDC"
assetVersion: "2"
description: "API access"

routers:
paid:
rule: "Host(`api.example.com`)"
entryPoints: [web]
service: my-upstream@file
middlewares: [x402-pay@file]
```

---

## Compatibility

| Dimension | Supported |
|---|---|
| Traefik | v3.7+ (older versions fail against Docker Engine 29's `MinAPIVersion=1.44`) |
| Networks (public facilitator) | `base`, `base-sepolia` |
| Default asset | USDC |
| Plugin runtime dependencies | none (stdlib only) |

---

## Try it locally

A one-command Docker Compose demo with Traefik + a dummy upstream + the plugin loaded via Yaegi is included. See **[docs/dev.md](docs/dev.md)** for the quickstart, the unit / end-to-end test workflow, and troubleshooting.

---

## Roadmap

- Pluggable facilitator selection per-route
- Prometheus metrics (verify/settle latency, failure rates)
- Structured JSON logging
- Response streaming (currently buffered)
- Payment caching via cookie or JWT to avoid re-verification for the same payer across requests
- Solana and Stellar network support once public facilitators are available

---

## Contributing

Pull requests welcome. See [docs/dev.md](docs/dev.md) for setup, test commands, and dev environment details. Open an [issue](https://github.com/albttx/traefik-plugin-x402/issues) first to discuss non-trivial changes.

---

## License

MIT — see [LICENSE](LICENSE).

---

## Acknowledgements

Implements the [x402 protocol](https://x402.org) designed and specified by [Coinbase](https://github.com/coinbase/x402). The plugin system is provided by [Traefik](https://traefik.io).