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
endEvent Types
| Event | Description |
|---|---|
IDENTIFICATION | KYC identification status changed |
PERSON_CHANGED | Person data updated via change request |
BOOKING | New booking on an account |
SCA_CHALLENGE | 3DS / SCA challenge requires action |
INSTANT_SEPA_CREDIT_TRANSFER_FAILED | Instant SCT failed |
CASH_OPERATION_STATUS_CHANGED | Viacash operation status changed |
CONSUMER_LOAN_APPLICATION | Loan application status changed |
CREDIT_CARD_APPLICATION | Credit card application status changed |
BUSINESS_IDENTIFICATION | Business KYC status changed |
POSTBOX_ITEM_CREATED | New document in customer postbox |
TAX_EXEMPTION_UTILIZATION | Tax exemption utilization data ready |
ACCOUNT_SNAPSHOT_COMPLETED | Account snapshot session completed |
TRADE_FINANCE_APPLICATION | Trade finance application status changed |
TRADE_FINANCE_TRADE | Trade status changed |
SIGNING_COMPLETED | Document 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
@type event() :: %{ event_type: String.t(), delivery_id: String.t(), payload: map(), received_at: DateTime.t() }
Functions
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)
@spec known_event_types() :: [String.t()]
Returns all known Solaris webhook event type strings.
Parses a raw JSON webhook body without signature verification.
Warning: Only use after the signature has already been verified.
@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
@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 thesolaris-webhook-signaturerequest headersecret— 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