X402
The Elixir SDK for the x402 HTTP payment protocol.
x402 is an open standard for internet-native payments built around the HTTP 402 Payment Required status code. This library provides everything you need to accept or make x402 payments in Elixir.
Features
- Protocol primitives — encode/decode
PAYMENT-REQUIRED,PAYMENT-SIGNATURE, andPAYMENT-RESPONSEheaders - Facilitator client — verify and settle payments via any x402 facilitator
- Plug middleware — drop-in payment gate for Phoenix and Plug apps
- Lifecycle hooks — before/after/failure callbacks for verify & settle flows
- Payment identifier — idempotency extension with pluggable cache
- "upto" scheme — max-price bidding for flexible payments
- SIWX (Sign-In-With-X) — wallet-authenticated repeat access without repayment
- Wallet validation — EVM (Ethereum, Base, Polygon) and Solana address validation
- Zero lock-in — works with any facilitator, any chain, any framework
- Fully typed — comprehensive typespecs and Dialyzer-clean
Installation
Add x402 to your list of dependencies in mix.exs:
def deps do
[
{:x402, "~> 0.3"},
{:finch, "~> 0.19"}, # HTTP client (required for facilitator calls)
{:ex_secp256k1, "~> 0.8"}, # Only if using SIWX signature verification
{:ex_keccak, "~> 0.7"} # Only if using SIWX signature verification
]
end
finch,ex_secp256k1, andex_keccakare optional. Only add what you need.
Quick Start
Accept payments in Phoenix
# In your router or endpoint
plug X402.Plug.PaymentGate,
facilitator_url: "https://x402-facilitator-app.fly.dev",
routes: %{
"GET /api/weather" => %{
price: "0.005",
network: "eip155:8453",
pay_to: "0xYourWalletAddress",
description: "Weather data API"
}
}
That's it. Requests without payment get a 402 response with payment instructions. Requests with a valid PAYMENT-SIGNATURE header are verified and passed through.
With lifecycle hooks
defmodule MyApp.PaymentHooks do
@behaviour X402.Hooks
@impl true
def before_verify(context, _metadata) do
IO.inspect(context.payment, label: "Incoming payment")
{:ok, context}
end
@impl true
def after_verify(context, _metadata) do
# Log successful verification, trigger webhooks, etc.
{:ok, context}
end
@impl true
def after_settle(context, _metadata) do
# Post-settlement logic: update DB, send receipt
{:ok, context}
end
@impl true
def on_verify_failure(context, _metadata), do: {:ok, context}
@impl true
def before_settle(context, _metadata), do: {:ok, context}
@impl true
def on_settle_failure(context, _metadata), do: {:ok, context}
end
# Use in PaymentGate
plug X402.Plug.PaymentGate,
facilitator_url: "https://x402-facilitator-app.fly.dev",
hooks: MyApp.PaymentHooks,
routes: %{...}"upto" scheme — flexible pricing
# Server: accept up to a max price (agent bids what they're willing to pay)
plug X402.Plug.PaymentGate,
facilitator_url: "https://x402-facilitator-app.fly.dev",
routes: %{
"GET /api/premium" => %{
scheme: "upto",
maxPrice: "1.00",
network: "eip155:8453",
pay_to: "0xYourWallet",
description: "Premium data — pay what you want up to $1"
}
}
# Encode/decode upto payment requirements
{:ok, header} = X402.PaymentRequired.encode(%{
accepts: [%{
scheme: "upto",
network: "eip155:8453",
maxPrice: "1.00",
pay_to: "0xYourWallet"
}],
description: "Pay up to $1"
})Payment identifier — idempotency
Prevent duplicate payments by attaching unique payment IDs:
# Start the ETS cache in your supervision tree
children = [
{X402.Extensions.PaymentIdentifier.ETSCache, []}
]
# Encode a payment ID into a payload
{:ok, encoded} = X402.Extensions.PaymentIdentifier.encode("pay-abc-123")
# Decode it back
{:ok, payment_id} = X402.Extensions.PaymentIdentifier.decode(encoded)
#=> {:ok, "pay-abc-123"}
# Check/store in cache for deduplication
:ok = X402.Extensions.PaymentIdentifier.ETSCache.put("pay-abc-123")
{:ok, true} = X402.Extensions.PaymentIdentifier.ETSCache.exists?("pay-abc-123")SIWX — Sign-In-With-X (repeat access)
Let paying users prove wallet ownership to access content again without repaying:
# 1. Build a SIWX challenge message (CAIP-122 / EIP-4361)
message = X402.Extensions.SIWX.build_message(%{
domain: "api.example.com",
address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
uri: "https://api.example.com/premium",
chain_id: 8453,
nonce: X402.Extensions.SIWX.generate_nonce(),
statement: "Sign in to access premium content"
})
# 2. Verify the wallet's signature (EVM)
{:ok, true} = X402.Extensions.SIWX.Verifier.Default.verify_signature(
message,
signature_hex,
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
)
# 3. Store access record with TTL
{:ok, _pid} = X402.Extensions.SIWX.ETSStorage.start_link([])
:ok = X402.Extensions.SIWX.ETSStorage.put(
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"/premium",
%{tx: "0xabc..."},
:timer.hours(24) # 24-hour access
)
# 4. Check if wallet has valid access
{:ok, record} = X402.Extensions.SIWX.ETSStorage.get(
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"/premium"
)Encode/decode headers manually
# Build a PAYMENT-REQUIRED response
{:ok, header} = X402.PaymentRequired.encode(%{
accepts: [%{
scheme: "exact",
network: "eip155:8453",
price: "0.01",
pay_to: "0xYourWallet"
}],
description: "Premium API access"
})
# Decode an incoming PAYMENT-SIGNATURE
{:ok, payload} = X402.PaymentSignature.decode(signature_header)
# Verify payment via facilitator
{:ok, result} = X402.Facilitator.verify(payload, requirements)Verify payments programmatically
# Start the facilitator client in your supervision tree
children = [
{Finch, name: MyApp.Finch},
{X402.Facilitator, name: MyApp.Facilitator, finch: MyApp.Finch}
]
# Then verify payments
{:ok, %{status: 200}} = X402.Facilitator.verify(
MyApp.Facilitator,
payment_payload,
payment_requirements
)Architecture
┌─────────────┐ ┌──────────────────────┐ ┌──────────────┐
│ AI Agent │────▶│ Your API + x402 │────▶│ Facilitator │
│ (payer) │◀────│ PaymentGate Plug │◀────│ (verify/ │
└─────────────┘ │ │ │ settle) │
│ ┌──────────────┐ │ └──────────────┘
│ │ Hooks │ │
│ │ PaymentID │ │
│ │ SIWX Storage │ │
│ └──────────────┘ │
└──────────────────────┘Documentation
Full documentation is available at HexDocs.
x402 Protocol
x402 is an open standard by Coinbase for HTTP-native payments:
- Client requests a paid resource
-
Server returns
402 Payment Requiredwith pricing info - Client pays (USDC on Base, Solana, etc.)
-
Client retries with
PAYMENT-SIGNATUREheader - Server verifies via facilitator and serves the resource
Learn more at x402.org and docs.x402.org.
Related
x402_proxy— reverse proxy that adds x402 payment gating to any APIx402_facilitator— x402 facilitator + payment gateway (live)- x402 TypeScript SDK — official Coinbase SDK
- x402 Python SDK — Python implementation
License
MIT License — see LICENSE for details.