Plug scaffold exposing one or more facilitator engines as the facilitator HTTP API.
Serves the x402 v2 facilitator endpoints over any Plug-compatible server (Bandit, Cowboy, or mounted inside a Phoenix endpoint):
| Endpoint | Behaviour |
|---|---|
POST /verify | the matching engine's verify/3 |
POST /settle | the matching engine's settle/3 |
GET /supported | the engines' supported/1, merged |
GET /discovery/resources | 404 — bazaar discovery serving is not included |
Usage
{:ok, engine} =
X402.Facilitator.Engine.new(
rpc: rpc,
signer: signer,
networks: ["eip155:84532"]
)
children = [
{Finch, name: MyApp.Finch},
{Bandit, plug: {X402.Plug.Facilitator, engine: engine}, port: 4022}
]Mount the plug at the root of its listener (or behind a forward that
strips the prefix): it answers 404 for paths it does not serve.
Multiple engines
A facilitator serving several chain families passes :engines instead
of :engine — for example an EVM X402.Facilitator.Engine next to an
SVM X402.Facilitator.SVMEngine:
{X402.Plug.Facilitator, engines: [evm_engine, svm_engine]}Exactly one of :engine and :engines must be given. POST /verify
and POST /settle dispatch to the first engine whose supported/1
kinds contain the request's paymentRequirements (scheme, network)
pair; when none matches, the request is answered with a 200
protocol rejection (unsupported_scheme, or invalid_network when
some engine serves the scheme on other networks). GET /supported
merges the engines' responses — kinds concatenated, extensions
unioned, signer families merged. Any struct whose module exports
verify/3, settle/3, and supported/1 with the engine wire
contract can be listed.
Request/response contract
POST /verify and POST /settle require exactly the v2 facilitator
wire object — {"x402Version": 2, "paymentPayload": {...}, "paymentRequirements": {...}} — and reject anything else with 400.
Following the facilitator API convention, protocol-level rejections are
200 responses ({"isValid": false, ...} / {"success": false, ...}); non-2xx statuses are reserved for transport-level problems:
| Status | Meaning |
|---|---|
200 | Engine verdict (including invalid / failed payments) |
400 | Malformed body: bad JSON or not the v2 wire object |
401 | Missing/wrong bearer token (when :auth_token is configured) |
404 | Unknown path (including /discovery/resources) |
405 | Known path, wrong method |
413 | Body larger than :max_body_bytes |
500 | Engine infrastructure error — opaque body, details are logged |
Authentication
The optional :auth_token enables a minimal bearer-token check
(constant-time comparison) on every endpoint. It is a convenience for
private deployments — put real authentication, TLS termination, and
rate limiting in front of a production facilitator.
Summary
Functions
Dispatches a facilitator API request — see the module documentation for the endpoint and status contract.
Validates the plug options.
Types
@type options() :: %{ engines: [struct()], auth_token: String.t() | nil, max_body_bytes: pos_integer() }
Validated plug options.
Functions
@spec call(Plug.Conn.t(), options()) :: Plug.Conn.t()
Dispatches a facilitator API request — see the module documentation for the endpoint and status contract.
Validates the plug options.
Exactly one of :engine and :engines must be given; both are
normalized to a list of engines internally.
Options
:engine- A singleX402.Facilitator.Engineconfiguration built withEngine.new/1. Exactly one of:engineor:enginesmust be given.:engines- A non-empty list of engine structs (X402.Facilitator.Engine,X402.Facilitator.SVMEngine, or any struct whose module exportsverify/3,settle/3, andsupported/1), dispatched by the request's(scheme, network). Exactly one of:engineor:enginesmust be given.:auth_token- Optional bearer token required on every request (compared in constant time).nildisables authentication — front a production deployment with real auth instead. The default value isnil.:max_body_bytes(pos_integer/0) - Maximum accepted request body size, consistent with the SDK's 8KB encoded-header caps. Larger bodies answer413. The default value is8192.