ClearBank.Webhook.Handler behaviour (ClearBank v1.0.0)

Copy Markdown View Source

Behaviour for implementing typed ClearBank webhook handlers.

Usage

Implement this behaviour in your application to handle specific webhook types:

defmodule MyApp.ClearBankWebhookHandler do
  use ClearBank.Webhook.Handler

  @impl true
  def handle(%ClearBank.Webhook{type: "TransactionSettled"} = webhook) do
    payload = webhook.payload
    # update your ledger, notify customer, etc.
    :ok
  end

  def handle(%ClearBank.Webhook{type: "FITestEvent"}) do
    :ok
  end

  def handle(webhook) do
    Logger.warning("Unhandled ClearBank webhook: #{webhook.type}")
    :ok
  end
end

Plug integration

Use with a Plug-based router in your Phoenix/Plug app. Critical: capture the raw body before parsing JSON, so you can verify the signature.

# In your Router:
post "/webhooks/clearbank" do
  {:ok, raw_body, conn} = Plug.Conn.read_body(conn)
  signature = List.first(get_req_header(conn, "digitalsignature"))

  with :ok <- ClearBank.Webhook.Verifier.verify(raw_body, signature, pub_key_pem()),
       {:ok, body_map} <- Jason.decode(raw_body),
       {:ok, webhook} <- ClearBank.Webhook.parse(body_map),
       :ok <- MyApp.ClearBankWebhookHandler.handle(webhook) do

    ack_body = Jason.encode!(ClearBank.Webhook.ack_body(webhook))
    sig = ClearBank.Auth.Signer.sign!(ack_body, my_private_key())

    conn
    |> put_resp_header("digitalsignature", sig)
    |> put_resp_content_type("application/json")
    |> send_resp(200, ack_body)
  else
    {:error, :invalid_signature} -> send_resp(conn, 401, "")
    _ -> send_resp(conn, 500, "")
  end
end

Idempotency

ClearBank guarantees at-least-once delivery. Always check for duplicate webhooks before side-effectful processing. Compare type + id from the payload to detect duplicates.

Response timing

Respond within 5 seconds. For slow processing, queue the event and respond immediately, then process asynchronously.

Summary

Callbacks

Handles an inbound webhook. Return :ok on success, {:error, reason} on failure.

Callbacks

handle(t)

@callback handle(ClearBank.Webhook.t()) :: :ok | {:error, term()}

Handles an inbound webhook. Return :ok on success, {:error, reason} on failure.