GoCardlessClient.Webhooks.Plug (GoCardlessClient v2.0.0)

Copy Markdown View Source

A Plug that verifies GoCardlessClient webhook signatures and parses events.

Parsed events are stored in conn.private[:gocardless_events] for downstream handlers to consume. The raw body is stored in conn.private[:gocardless_raw_body].

On signature failure the plug halts the connection and returns HTTP 401. On JSON parse failure it returns HTTP 400.

Setup in Phoenix

First, ensure the raw body is preserved by configuring Plug.Parsers with a custom body reader (required because Plug consumes the body by default):

# In your endpoint.ex:
plug Plug.Parsers,
  parsers: [:urlencoded, :multipart, :json],
  pass: ["*/*"],
  json_decoder: Jason,
  body_reader: {GoCardlessClient.Webhooks.Plug, :read_body, []}

Then add a pipeline and route in your router:

pipeline :gocardless_webhooks do
  plug GoCardlessClient.Webhooks.Plug, secret: System.get_env("GOCARDLESS_WEBHOOK_SECRET")
end

scope "/webhooks" do
  pipe_through :gocardless_webhooks
  post "/gocardless", MyApp.WebhookController, :handle
end

In your controller

def handle(conn, _params) do
  events = conn.private[:gocardless_events]

  Enum.each(events, fn event ->
    case {event["resource_type"], event["action"]} do
      {"payments", "paid_out"} -> handle_payment_paid_out(event)
      {"mandates", "active"}   -> handle_mandate_active(event)
      {"billing_requests", "fulfilled"} -> handle_br_fulfilled(event)
      _ -> :ok
    end
  end)

  send_resp(conn, 200, "")
end

Options

  • :secret (required) — your webhook endpoint secret from the GoCardlessClient dashboard.
  • :max_body_bytes — maximum allowed body size in bytes (default: 10 MB).

Summary

Functions

Custom body reader for use with Plug.Parsers.

Functions

read_body(conn, opts)

@spec read_body(
  Plug.Conn.t(),
  keyword()
) ::
  {:ok, binary(), Plug.Conn.t()}
  | {:more, binary(), Plug.Conn.t()}
  | {:error, term()}

Custom body reader for use with Plug.Parsers.

Reads and caches the raw body in conn.private[:gocardless_raw_body] before Plug.Parsers consumes it, allowing GoCardlessClient.Webhooks.Plug to access it later.

Add to your endpoint.ex:

plug Plug.Parsers,
  parsers: [:json],
  json_decoder: Jason,
  body_reader: {GoCardlessClient.Webhooks.Plug, :read_body, []}