Current section
Files
Jump to
Current section
Files
README.md
# X402
[](https://hex.pm/packages/x402)
[](https://hex.pm/packages/x402)
[](https://hexdocs.pm/x402)
[](https://github.com/cardotrejos/x402/actions/workflows/ci.yml)
[](LICENSE)
The Elixir SDK for the [x402](https://x402.org) HTTP payment protocol.
X402 is a library, not an application. It provides protocol headers, a facilitator
client, and optional Plug middleware without tying an application to a specific
facilitator, chain, or web framework.
## Features
- x402 v2 `PAYMENT-REQUIRED`, `PAYMENT-SIGNATURE`, and `PAYMENT-RESPONSE` headers
- Complete v2 payment-requirement and extension-echo validation
- Facilitator `/verify` and `/settle` client with retries, hooks, and telemetry
- Plug/Phoenix payment gate that settles only after successful resource handling
- `"exact"` and metered `"upto"` authorization flows
- Optional payment-identifier idempotency cache and SIWX support
- EVM and Solana wallet validation
- Optional Finch, Plug, and cryptography dependencies
## Installation
Add the library and only the optional integrations your application uses:
```elixir
def deps do
[
{:x402, "~> 0.4"},
{:finch, "~> 0.19"}, # facilitator HTTP calls
{:plug, "~> 1.14"} # PaymentGate
]
end
```
Add `ex_secp256k1` and `ex_keccak` only when using the default EVM SIWX
signature verifier.
## Phoenix quick start
Start Finch, the facilitator client, and the idempotency cache in your
application supervision tree:
```elixir
children = [
{Finch,
name: MyApp.Finch,
pools: %{default: X402.Facilitator.HTTP.secure_pool_opts()}},
{X402.Facilitator,
name: MyApp.Facilitator,
url: "https://facilitator.example.com",
finch: MyApp.Finch},
{X402.Extensions.PaymentIdentifier.ETSCache, name: MyApp.PaymentCache}
]
```
Configure the Plug with the facilitator process, not a URL:
```elixir
plug X402.Plug.PaymentGate,
facilitator: MyApp.Facilitator,
payment_identifier_cache: MyApp.PaymentCache,
routes: [
%{
method: :get,
path: "/api/weather",
scheme: "exact",
price: "10000", # atomic units: 0.01 USDC when the asset has 6 decimals
network: "eip155:8453",
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
pay_to: "0xYourWalletAddress",
description: "Weather data API"
}
]
```
An unpaid request receives HTTP 402 and a Base64-encoded v2
`PAYMENT-REQUIRED` header. A paid request is decoded and matched against the
complete advertised requirement, verified, passed to the protected handler,
and settled immediately before a successful response is sent. Handler responses
with status 400 or greater are not settled.
The verified payload and matched requirement are available to the handler as
`conn.assigns.x402_payment_payload` and
`conn.assigns.x402_payment_requirements`.
## Metered `"upto"` payments
For an `"upto"` route, `price` is the maximum authorization in atomic token
units:
```elixir
%{
method: :post,
path: "/api/generate",
scheme: "upto",
price: "1000000", # authorize up to 1 USDC for a 6-decimal asset
network: "eip155:8453",
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
pay_to: "0xYourWalletAddress"
}
```
After measuring resource use, store the actual amount on the connection before
building the response:
```elixir
def create(conn, params) do
result = generate(params)
actual_atomic_amount = billable_amount(result)
{:ok, conn} =
X402.Plug.PaymentGate.put_settlement_amount(conn, actual_atomic_amount)
json(conn, %{result: result})
end
```
The facilitator receives the advertised maximum during `/verify` and the actual
amount during `/settle`. An amount above the authorized maximum fails closed.
If no actual amount is supplied, the advertised maximum is settled.
This release implements the post-handler `authorization` flow used by current
EVM `exact` and `upto` schemes. Route options declaring `paymentFlow: "upfront"`
or `paymentFlow: "escrow"` are rejected because those flows require different
handler and cancellation semantics.
## Lifecycle hooks
Hooks receive an `X402.Hooks.Context` and must use the return contract defined by
`X402.Hooks`:
```elixir
defmodule MyApp.PaymentHooks do
@behaviour X402.Hooks
@impl true
def before_verify(context, _metadata) do
IO.inspect(context.payload, label: "Incoming payment")
{:cont, context}
end
@impl true
def after_verify(context, _metadata), do: {:cont, context}
@impl true
def on_verify_failure(context, _metadata), do: {:cont, context}
@impl true
def before_settle(context, _metadata), do: {:cont, context}
@impl true
def after_settle(context, _metadata), do: {:cont, context}
@impl true
def on_settle_failure(context, _metadata), do: {:cont, context}
end
```
Pass the module with `hooks: MyApp.PaymentHooks`. Before hooks may return
`{:halt, reason}`; failure hooks may return `{:recover, result}`.
## Multiple payment options
Use `accepts` to advertise more than one valid requirement:
```elixir
%{
method: :get,
path: "/api/data",
accepts: [
%{
scheme: "exact",
price: "10000",
network: "eip155:8453",
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
pay_to: "0xYourWalletAddress"
},
%{
scheme: "exact",
price: "5000",
network: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
asset: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
pay_to: "YourSolanaAddress"
}
]
}
```
Every core field must match exactly. Client-added metadata is allowed only
under `accepted.extra` and cannot remove or mutate values advertised by the
server.
## Facilitator API
The lower-level client can be called directly:
```elixir
case X402.Facilitator.verify(
MyApp.Facilitator,
payment_payload,
payment_requirements
) do
{:ok, %{status: 200, body: %{"isValid" => true} = result}} ->
{:ok, result}
{:ok, %{status: 200, body: %{"isValid" => false} = result}} ->
{:error, result}
{:error, reason} ->
{:error, reason}
end
```
Facilitator requests use the v2 wire object:
`%{"x402Version" => 2, "paymentPayload" => payload,
"paymentRequirements" => requirements}`.
## HTTP outcomes
`X402.Plug.PaymentGate` follows the v2 HTTP transport mapping:
| Status | Meaning |
|--------|---------|
| 400 | Malformed or invalid payment input |
| 402 | Payment required, unmatched terms, or verification/settlement failure |
| 500 | Facilitator transport failure, malformed facilitator response, or internal payment-processing error |
## Documentation
- [Getting Started](https://hexdocs.pm/x402/getting-started.html)
- [Plug/Phoenix Integration](https://hexdocs.pm/x402/plug-integration.html)
- [API Reference](https://hexdocs.pm/x402/api-reference.html)
- [Official x402 v2 specification](https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md)
- [Official HTTP transport](https://github.com/x402-foundation/x402/blob/main/specs/transports-v2/http.md)
## License
MIT License — see [LICENSE](LICENSE) for details.