Nombaone. Webhooks
(Nomba One v0.1.0)
View Source
Verify and parse incoming NombaOne webhook deliveries.
This is pure crypto — no API key or client needed, only the endpoint's signing secret (shown once at create/rotate). Every function is a plain module function.
Feed it the raw body
Decoding then re-encoding the JSON can reorder keys and change bytes, which
breaks the signature. Capture the body before any framework parses it. In
Phoenix, use a custom body reader that stashes the raw bytes (see the README),
or read conn before Plug.Parsers.
Verify, then dedupe
case Nombaone.Webhooks.construct_event(raw_body, signature_header, secret) do
{:ok, event} ->
unless already_processed?(event.event.id) do # at-least-once ⇒ dedupe
handle(event)
end
send_resp(conn, 200, "")
{:error, %Nombaone.WebhookVerificationError{} = error} ->
send_resp(conn, 400, Exception.message(error))
end
Summary
Types
Options for verification. :tolerance is max clock skew in seconds (default 300), or :infinity to skip the age check.
Functions
Verify a delivery's signature and timestamp, then parse and return the typed event. This is the one call your handler needs.
Raising variant of construct_event/4.
Build a valid X-Nombaone-Signature header for a payload — to test your own
handler without waiting on a real delivery. Options: :timestamp (unix
seconds, defaults to now).
Verify a delivery's signature and timestamp only (no parse). Returns :ok, or
{:error, %Nombaone.WebhookVerificationError{}} with a distinct message per
failure mode.
Types
@type verify_opts() :: [{:tolerance, non_neg_integer() | :infinity}]
Options for verification. :tolerance is max clock skew in seconds (default 300), or :infinity to skip the age check.
Functions
@spec construct_event(iodata(), String.t() | nil, String.t() | nil, verify_opts()) :: {:ok, Nombaone.WebhookEvent.t()} | {:error, Nombaone.WebhookVerificationError.t()}
Verify a delivery's signature and timestamp, then parse and return the typed event. This is the one call your handler needs.
Returns {:ok, %Nombaone.WebhookEvent{}}, or {:error, %Nombaone.WebhookVerificationError{}} on a missing/malformed header, a stale
timestamp, an invalid signature, or a non-JSON body.
payload may be a binary or iodata (the raw request body); signature_header
is the X-Nombaone-Signature value; secret is the endpoint's signing secret.
@spec construct_event!(iodata(), String.t() | nil, String.t() | nil, verify_opts()) :: Nombaone.WebhookEvent.t()
Raising variant of construct_event/4.
Build a valid X-Nombaone-Signature header for a payload — to test your own
handler without waiting on a real delivery. Options: :timestamp (unix
seconds, defaults to now).
Example
payload = Jason.encode!(%{id: "nbo…whd", type: "invoice.paid", event: %{id: "nbo…evt", type: "invoice.paid", createdAt: "..."}, data: %{reference: "nbo…inv"}})
header = Nombaone.Webhooks.generate_test_header(payload, "whsec_test")
{:ok, event} = Nombaone.Webhooks.construct_event(payload, header, "whsec_test")
@spec verify_signature(iodata(), String.t() | nil, String.t() | nil, verify_opts()) :: :ok | {:error, Nombaone.WebhookVerificationError.t()}
Verify a delivery's signature and timestamp only (no parse). Returns :ok, or
{:error, %Nombaone.WebhookVerificationError{}} with a distinct message per
failure mode.