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
endPlug 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
endIdempotency
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
@callback handle(ClearBank.Webhook.t()) :: :ok | {:error, term()}
Handles an inbound webhook. Return :ok on success, {:error, reason} on failure.