X402.Paywall behaviour (X402 v0.6.0)

Copy Markdown View Source

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
end

Returning {:error, reason} falls back to the default JSON body, so a renderer failure never blocks the payment flow.

Summary

Types

Request details passed to render/2.

Callbacks

Renders the HTML paywall page for a v2 PaymentRequired payload.

Types

conn_info()

@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

render(payment_required, conn_info)

@callback render(payment_required :: map(), conn_info()) ::
  {:ok, iodata()} | {:error, term()}

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.