# `TruelayerClient.Webhooks`
[🔗](https://github.com/iamkanishka/truelayer_client/blob/v1.0.0/lib/truelayer_client/webhooks.ex#L1)

TrueLayer webhook signature verification, replay-attack protection, and typed dispatch.

## Security model

  1. **HMAC-SHA256 verification** — constant-time comparison prevents timing attacks
  2. **Replay protection** — events older than `:webhook_replay_tolerance_sec` are rejected
  3. **Typed dispatch** — handlers registered per event type via `on/3`

## Usage in a Phoenix controller

Add a `CacheBodyReader` plug to preserve the raw body for signature verification,
then call `process/4` in your controller:

    defmodule MyAppWeb.WebhookController do
      use MyAppWeb, :controller

      def truelayer(conn, _params) do
        raw_body = conn.assigns[:raw_body]
        sig = get_req_header(conn, "tl-signature") |> List.first()
        ts  = get_req_header(conn, "tl-timestamp")  |> List.first()

        case TruelayerClient.Webhooks.process(client, raw_body, sig, ts) do
          :ok                -> send_resp(conn, 200, "")
          {:error, :bad_sig} -> send_resp(conn, 401, "invalid signature")
          {:error, :replay}  -> send_resp(conn, 401, "event too old")
          {:error, reason}   -> send_resp(conn, 500, inspect(reason))
        end
      end
    end

## Registering handlers

    TruelayerClient.Webhooks.on(client, TruelayerClient.Webhooks.payment_executed(), fn event ->
      %{"payload" => %{"payment_id" => id}} = event
      MyApp.Payments.finalize(id)
      :ok
    end)

## Event type constants

Use the named functions (`payment_executed/0`, etc.) rather than raw strings
to avoid typos:

    TruelayerClient.Webhooks.on(client, TruelayerClient.Webhooks.refund_executed(), handler)

# `event_type`

```elixir
@type event_type() :: String.t()
```

# `handler_fn`

```elixir
@type handler_fn() :: (map() -&gt; :ok | {:error, term()})
```

# `registry`

```elixir
@type registry() :: :ets.tid()
```

# `account_holder_verification_completed`

Account holder verification completed.

# `account_holder_verification_failed`

Account holder verification failed.

# `identity_authorization_expired`

Signup+ authorization URI expired.

# `mandate_authorized`

Mandate authorized by the PSU.

# `mandate_failed`

Mandate failed.

# `mandate_revoked`

Mandate revoked.

# `merchant_account_payment_failed`

Merchant account payment failed.

# `merchant_account_payment_settled`

Merchant account payment settled.

# `new_registry`

```elixir
@spec new_registry() :: registry()
```

Create a new handler registry (ETS `:bag` table). Called once per client.

# `on`

```elixir
@spec on(TruelayerClient.t(), event_type(), handler_fn()) :: :ok
```

Register a handler for a specific event type.

Multiple handlers per event type are supported; all are called in registration
order. If a handler returns `{:error, reason}`, dispatch halts and the error
is propagated, causing TrueLayer to retry the webhook delivery.

## Example

    TruelayerClient.Webhooks.on(client, TruelayerClient.Webhooks.payment_executed(), fn event ->
      id = get_in(event, ["payload", "payment_id"])
      MyApp.Payments.handle_executed(id)
      :ok
    end)

# `on_fallback`

```elixir
@spec on_fallback(TruelayerClient.t(), handler_fn()) :: :ok
```

Register a fallback handler called for any event type with no registered handler.

# `payment_authorized`

Payment authorized by the PSU.

# `payment_executed`

Payment successfully executed.

# `payment_failed`

Payment failed.

# `payment_link_payment_executed`

Payment link payment executed.

# `payment_settled`

Payment funds settled into the merchant account.

# `payout_executed`

Payout executed.

# `payout_failed`

Payout failed.

# `process`

```elixir
@spec process(TruelayerClient.t(), binary(), String.t() | nil, String.t() | nil) ::
  :ok | {:error, term()}
```

Verify and dispatch a raw webhook payload.

## Parameters

  * `client` — `TruelayerClient.t()` with webhook configuration
  * `body` — raw request body binary (must not be parsed before calling this)
  * `signature` — value of the `Tl-Signature` request header
  * `timestamp` — value of the `Tl-Timestamp` request header (RFC 3339)

## Return values

  * `:ok` — verified and successfully dispatched
  * `{:error, :bad_sig}` — signature verification failed
  * `{:error, :replay}` — event is outside the replay tolerance window
  * `{:error, {:decode_error, reason}}` — JSON parsing failed
  * `{:error, reason}` — a registered handler returned an error

# `refund_executed`

Refund executed.

# `refund_failed`

Refund failed.

# `vrp_payment_executed`

VRP payment executed.

# `vrp_payment_failed`

VRP payment failed.

---

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