# `X402.Hooks`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/hooks.ex#L1)

Behaviour for lifecycle hooks around facilitator verify and settle operations.

Hooks run around each operation in this order:

1. `before_verify/2` or `before_settle/2`
2. `after_verify/2` or `after_settle/2` on success
3. `on_verify_failure/2` or `on_settle_failure/2` on failure

`before_*` callbacks can continue with `{:cont, context}` or abort with
`{:halt, reason}`.

`on_*_failure` callbacks can continue failure handling with `{:cont, context}`
or recover the operation with `{:recover, result}`.

Hook callbacks are invoked in the process that calls
`X402.Facilitator.verify/2` or `X402.Facilitator.settle/2` (for example a
Plug request process), not in the facilitator process. Hooks that touch
process-bound state should account for running concurrently across callers.

## Resource-server hooks

Two further callbacks are **optional** and are only invoked by the
resource-server transports (`X402.Plug.PaymentGate` and `X402.MCP.Server`)
when the hook module defines them. They mirror the reference resource
server's `onProtectedRequest` and `onVerifiedPaymentCanceled` hooks:

* `c:on_protected_request/2` runs for every request that matches a gated
  route (or paid tool) before any payment processing, with an
  `X402.Hooks.RequestContext`. It may continue with `{:cont, context}` —
  optionally replacing `context.requirements` or `context.extensions` for
  this request, for example to apply a per-caller discount — answer the
  request directly with `{:halt, {status, body}}`, or let the protected
  handler run unpaid with `{:halt, :skip_payment}` (an allowlisted API
  key, for instance). An exception or an unexpected return value fails
  closed: the transport answers with an internal error.
* `c:on_verified_payment_canceled/2` runs when a payment the facilitator
  already verified is **not** settled: the protected handler answered with
  a status of 400 or above (or, for MCP, returned an error result or
  raised), or settlement failed before a transaction was broadcast. Its
  return value is ignored and exceptions are caught and logged, so it is
  the place for compensating side effects (undo a quota increment, log a
  refundable authorization).

`X402.Hooks.Default` implements neither, so existing hook modules keep
working unchanged.

# `after_result`

```elixir
@type after_result() :: {:cont, X402.Hooks.Context.t()}
```

Return type for `after_*` callbacks.

# `before_result`

```elixir
@type before_result() :: {:cont, X402.Hooks.Context.t()} | {:halt, term()}
```

Return type for `before_*` callbacks.

# `callback_name`

```elixir
@type callback_name() ::
  :before_verify
  | :after_verify
  | :on_verify_failure
  | :before_settle
  | :after_settle
  | :on_settle_failure
  | :on_protected_request
  | :on_verified_payment_canceled
```

Hook callback identifier.

# `cancel_metadata`

```elixir
@type cancel_metadata() :: %{
  :transport =&gt; X402.Hooks.RequestContext.transport(),
  :hook_module =&gt; module(),
  :reason =&gt; cancellation_reason(),
  optional(:error) =&gt; term(),
  optional(:response_status) =&gt; pos_integer(),
  optional(:method) =&gt; atom(),
  optional(:path) =&gt; String.t(),
  optional(:route) =&gt; String.t(),
  optional(:tool) =&gt; String.t()
}
```

Metadata passed to `c:on_verified_payment_canceled/2`.

# `cancellation_reason`

```elixir
@type cancellation_reason() :: :handler_failed | :handler_raised | :settlement_failed
```

Why a verified payment was not settled.

* `:handler_failed` — the protected handler answered with a status of 400
  or above (HTTP) or returned an `isError` result (MCP); `:response_status`
  carries the HTTP status.
* `:handler_raised` — the MCP handler raised, threw, or exited; `:error`
  carries `{kind, reason}`.
* `:settlement_failed` — settlement failed before a transaction was
  broadcast; `:error` carries the failure reason.

# `hook_error`

```elixir
@type hook_error() ::
  {:hook_halted, callback_name(), term()}
  | {:hook_callback_failed, callback_name(), term()}
  | {:hook_invalid_return, callback_name(), term()}
```

Hook execution error tuple returned by `X402.Facilitator`.

# `metadata`

```elixir
@type metadata() :: %{
  operation: :verify | :settle,
  endpoint: String.t(),
  hook_module: module()
}
```

Lifecycle callback metadata passed to hooks.

# `on_failure_result`

```elixir
@type on_failure_result() :: {:cont, X402.Hooks.Context.t()} | {:recover, map()}
```

Return type for `on_*_failure` callbacks.

# `protected_request_result`

```elixir
@type protected_request_result() ::
  {:cont, X402.Hooks.RequestContext.t()}
  | {:halt, {pos_integer(), map()}}
  | {:halt, :skip_payment}
```

Return type for `c:on_protected_request/2`.

# `request_metadata`

```elixir
@type request_metadata() :: %{
  :transport =&gt; X402.Hooks.RequestContext.transport(),
  :hook_module =&gt; module(),
  optional(:method) =&gt; atom(),
  optional(:path) =&gt; String.t(),
  optional(:route) =&gt; String.t(),
  optional(:tool) =&gt; String.t()
}
```

Metadata passed to the resource-server request hooks.

# `after_settle`

```elixir
@callback after_settle(X402.Hooks.Context.t(), metadata()) :: after_result()
```

Runs after a successful settle request.

# `after_verify`

```elixir
@callback after_verify(X402.Hooks.Context.t(), metadata()) :: after_result()
```

Runs after a successful verify request.

# `before_settle`

```elixir
@callback before_settle(X402.Hooks.Context.t(), metadata()) :: before_result()
```

Runs before a settle request is sent.

# `before_verify`

```elixir
@callback before_verify(X402.Hooks.Context.t(), metadata()) :: before_result()
```

Runs before a verify request is sent.

# `on_protected_request`
*optional* 

```elixir
@callback on_protected_request(X402.Hooks.RequestContext.t(), request_metadata()) ::
  protected_request_result()
```

Runs before any payment processing for a request that matches a gated
route or paid tool. Optional.

# `on_settle_failure`

```elixir
@callback on_settle_failure(X402.Hooks.Context.t(), metadata()) :: on_failure_result()
```

Runs after a failed settle request.

# `on_verified_payment_canceled`
*optional* 

```elixir
@callback on_verified_payment_canceled(X402.Hooks.RequestContext.t(), cancel_metadata()) ::
  term()
```

Runs when a verified payment is not settled. Optional; the return value
is ignored.

# `on_verify_failure`

```elixir
@callback on_verify_failure(X402.Hooks.Context.t(), metadata()) :: on_failure_result()
```

Runs after a failed verify request.

# `run_protected_request`
*since 0.9.0* 

```elixir
@spec run_protected_request(module(), X402.Hooks.RequestContext.t(), map()) ::
  protected_request_result() | {:error, hook_error()}
```

Invokes the optional `c:on_protected_request/2` callback of a hook module.

Returns `{:cont, context}` untouched when the module does not define the
callback. A callback that raises, or returns anything other than a
`t:protected_request_result/0` with a valid replacement context (see
`X402.Hooks.RequestContext.valid?/1`), yields a `t:hook_error/0` so the
transport can fail closed. The `:transport` and `:hook_module` metadata
keys are filled in from the context and the module.

## Examples

    iex> context = X402.Hooks.RequestContext.new(requirements: [%{"scheme" => "exact"}])
    iex> X402.Hooks.run_protected_request(X402.Hooks.Default, context, %{path: "/api"})
    {:cont, context}

# `run_verified_payment_canceled`
*since 0.9.0* 

```elixir
@spec run_verified_payment_canceled(module(), X402.Hooks.RequestContext.t(), map()) ::
  :ok
```

Invokes the optional `c:on_verified_payment_canceled/2` callback of a hook
module.

A no-op when the module does not define the callback. The return value is
discarded; exceptions are caught and logged so a failing hook never masks
the response already being sent. The `:transport` and `:hook_module`
metadata keys are filled in from the context and the module; the caller
supplies `:reason` (a `t:cancellation_reason/0`) and its details.

## Examples

    iex> context = X402.Hooks.RequestContext.new(transport: :mcp, tool: "search")
    iex> metadata = %{reason: :handler_failed, tool: "search"}
    iex> X402.Hooks.run_verified_payment_canceled(X402.Hooks.Default, context, metadata)
    :ok

# `validate_module`
*since 0.1.0* 

```elixir
@spec validate_module(term()) :: {:ok, term()} | {:error, String.t()}
```

Validates that a value is a module implementing `X402.Hooks`.

This function is designed for `NimbleOptions` custom validation.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
