# `BankingCircle.Webhook.Verifier`
[🔗](https://github.com/iamkanishka/banking_circle/blob/main/lib/banking_circle/webhook/verifier.ex#L1)

Decrypts and verifies incoming Banking Circle webhook payloads.

Banking Circle secures webhook bodies with AES-256-GCM, using a
32-character pre-shared key set when you create the subscription (see
`BankingCircle.Webhooks.create_subscription/2`). Per the docs, a
received payload carries three companion values:

  * a **checksum** (44 base64 characters → 32 raw bytes)
  * an **authentication tag** (24 base64 characters → 16 raw bytes)
  * a **nonce** (16 base64 characters → 12 raw bytes)

This module treats the GCM authentication tag as the cryptographic
integrity guarantee (verified automatically by AEAD decryption itself —
decryption fails closed if the tag doesn't match). The **checksum** field
is additionally verified as a SHA-256 digest of the ciphertext, which is
the most common convention for this kind of "checksum" field; if your
account's payload docs specify a different checksum construction (e.g.
HMAC-SHA256 over ciphertext+AAD), override it via the `:checksum_fun`
option rather than trusting this default blindly — **verify this against
a real captured payload from your sandbox before relying on it in
production.**

## Usage in a Plug/Phoenix controller

    def webhook(conn, _params) do
      {:ok, raw_body, conn} = Plug.Conn.read_body(conn)

      case BankingCircle.Webhook.Verifier.verify_and_decrypt(raw_body,
             checksum: get_req_header(conn, "x-bc-checksum") |> List.first(),
             tag: get_req_header(conn, "x-bc-auth-tag") |> List.first(),
             nonce: get_req_header(conn, "x-bc-nonce") |> List.first(),
             key: Application.fetch_env!(:my_app, :bc_webhook_key)
           ) do
        {:ok, event} ->
          MyApp.WebhookHandler.handle(event)
          send_resp(conn, 200, "")

        {:error, reason} ->
          Logger.warning("Rejected Banking Circle webhook: #{inspect(reason)}")
          send_resp(conn, 400, "")
      end
    end

Header names above (`x-bc-checksum` etc.) are illustrative — confirm the
exact header names Banking Circle uses for your subscription against the
"Setting up Webhooks" guide, since this client doesn't hardcode them.

# `verify_opts`

```elixir
@type verify_opts() :: [
  checksum: String.t(),
  tag: String.t(),
  nonce: String.t(),
  key: String.t(),
  checksum_fun: (binary() -&gt; binary())
]
```

# `verify_and_decrypt`

```elixir
@spec verify_and_decrypt(binary(), verify_opts()) :: {:ok, term()} | {:error, atom()}
```

Verifies the checksum, verifies+decrypts via AES-256-GCM, and JSON-decodes
the plaintext.

Returns `{:ok, decoded_event}` or `{:error, reason}` where `reason` is
one of `:invalid_checksum`, `:decryption_failed` (bad tag/key/nonce —
treat as tampered or wrong key), or `:invalid_json`.

---

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