Solaris.Webhooks (Solaris v1.0.0)

Copy Markdown View Source

Webhook signature verification, event parsing, and handler dispatch.

Solaris sends webhook events as HTTP POST requests to your endpoint. Each request includes an HMAC-SHA256 signature header for authenticity verification.

Security

Always verify webhook signatures before processing events. Use verify_signature/3 or verify_and_parse/3 on every incoming request. Never process unverified payloads.

Phoenix / Plug Integration

See Solaris.Webhooks.Plug for a drop-in Plug that handles verification, parsing, and dispatch automatically.

Manual Integration

def handle_webhook(conn) do
  # Raw body MUST be read before any JSON parsing middleware
  raw_body = conn.assigns.raw_body
  signature = List.first(Plug.Conn.get_req_header(conn, "solaris-webhook-signature"))

  case Solaris.Webhooks.verify_and_parse(raw_body, signature, webhook_secret()) do
    {:ok, event} ->
      Solaris.Webhooks.dispatch(event, MyApp.SolarisHandler)
      send_resp(conn, 200, "ok")

    {:error, :invalid_signature} ->
      send_resp(conn, 401, "invalid signature")
  end
end

Event Types

EventDescription
IDENTIFICATIONKYC identification status changed
PERSON_CHANGEDPerson data updated via change request
BOOKINGNew booking on an account
SCA_CHALLENGE3DS / SCA challenge requires action
INSTANT_SEPA_CREDIT_TRANSFER_FAILEDInstant SCT failed
CASH_OPERATION_STATUS_CHANGEDViacash operation status changed
CONSUMER_LOAN_APPLICATIONLoan application status changed
CREDIT_CARD_APPLICATIONCredit card application status changed
BUSINESS_IDENTIFICATIONBusiness KYC status changed
POSTBOX_ITEM_CREATEDNew document in customer postbox
TAX_EXEMPTION_UTILIZATIONTax exemption utilization data ready
ACCOUNT_SNAPSHOT_COMPLETEDAccount snapshot session completed
TRADE_FINANCE_APPLICATIONTrade finance application status changed
TRADE_FINANCE_TRADETrade status changed
SIGNING_COMPLETEDDocument signing completed

Summary

Functions

Dispatches a parsed event to a handler implementing Solaris.Webhooks.Handler.

Returns all known Solaris webhook event type strings.

Parses a raw JSON webhook body without signature verification.

Verifies the signature and parses the event body in one step.

Verifies an HMAC-SHA256 webhook signature using constant-time comparison.

Types

event()

@type event() :: %{
  event_type: String.t(),
  delivery_id: String.t(),
  payload: map(),
  received_at: DateTime.t()
}

Functions

dispatch(event, handler_module)

@spec dispatch(event(), module()) :: :ok | {:ok, term()} | {:error, term()}

Dispatches a parsed event to a handler implementing Solaris.Webhooks.Handler.

Emits telemetry events on start and completion. Catches handler exceptions and logs them rather than propagating — returning {:error, exception}.

Examples

{:ok, event} = Solaris.Webhooks.verify_and_parse(body, sig, secret)
Solaris.Webhooks.dispatch(event, MyApp.WebhookHandler)

known_event_types()

@spec known_event_types() :: [String.t()]

Returns all known Solaris webhook event type strings.

parse_body(raw_body)

@spec parse_body(binary()) :: {:ok, event()} | {:error, :parse_error}

Parses a raw JSON webhook body without signature verification.

Warning: Only use after the signature has already been verified.

verify_and_parse(raw_body, signature, secret)

@spec verify_and_parse(binary(), String.t() | nil, String.t()) ::
  {:ok, event()}
  | {:error, :invalid_signature | :missing_signature | :parse_error}

Verifies the signature and parses the event body in one step.

Returns a parsed event map on success.

Examples

case Solaris.Webhooks.verify_and_parse(raw_body, signature, secret) do
  {:ok, event} ->
    Solaris.Webhooks.dispatch(event, MyApp.Handler)
  {:error, :invalid_signature} ->
    Logger.warning("Invalid webhook signature received")
end

verify_signature(raw_body, signature, secret)

@spec verify_signature(binary(), String.t() | nil, String.t()) ::
  :ok | {:error, :invalid_signature | :missing_signature}

Verifies an HMAC-SHA256 webhook signature using constant-time comparison.

Parameters

  • raw_body — Raw request body bytes (must not be JSON-decoded first)
  • signature — Value of the solaris-webhook-signature request header
  • secret — Your webhook secret (from the Solaris partner portal)

Returns

  • :ok — Signature is valid
  • {:error, :invalid_signature} — Signature mismatch
  • {:error, :missing_signature} — Header not present or empty

Examples

case Solaris.Webhooks.verify_signature(raw_body, signature, secret) do
  :ok -> process_event()
  {:error, reason} -> reject(reason)
end