Behaviour for rendering browser-facing HTML paywall pages.
When X402.Plug.PaymentGate is configured with paywall: module, 402
responses to requests that look like a browser page load carry an HTML body
rendered by the module instead of the default {} JSON body. A request is
treated as a browser page load when its Accept header contains
text/html and its User-Agent contains Mozilla — the same
heuristic the reference x402 middlewares use. The Base64 PAYMENT-REQUIRED
header is identical on both response forms; only the body differs.
X402.Paywall.Default ships a self-contained wallet-enabled page. Custom
implementations receive the exact v2 PaymentRequired payload the gate
encodes into the PAYMENT-REQUIRED header, so
X402.PaymentRequired.encode/1 reproduces the header value byte for byte:
defmodule MyApp.Paywall do
@behaviour X402.Paywall
@impl X402.Paywall
def render(payment_required, _conn_info) do
{:ok, ~s(<h1>#{payment_required["resource"]["description"]}</h1>)}
end
endReturning {:error, reason} falls back to the default JSON body, so a
renderer failure never blocks the payment flow.
Summary
Callbacks
Renders the HTML paywall page for a v2 PaymentRequired payload.
Types
@type conn_info() :: %{ method: String.t(), request_path: String.t(), status: pos_integer() }
Request details passed to render/2.
:method— the HTTP request method (for example"GET"):request_path— the decoded request path matched by the gate:status— the HTTP status of the response being rendered (always 402)
Callbacks
Renders the HTML paywall page for a v2 PaymentRequired payload.
payment_required is the string-keyed map the gate encodes into the
PAYMENT-REQUIRED response header (x402Version, error, resource,
accepts, extensions). Returns {:ok, html} with the complete page as
iodata, or {:error, reason} to fall back to the JSON body.