Webhook verification and parsing for Paysafe event notifications.
Paysafe sends real-time event notifications as HTTP POST requests with a
JSON body and a Signature header.
Security
Always verify the webhook signature before processing events. The signature is an HMAC-SHA256 of the raw request body using your webhook HMAC key, Base64-encoded:
digest = HMAC_SHA256(hmac_key, raw_body_utf8)
signature = Base64(digest)The signature appears in the Signature HTTP header.
Webhook event types
Payment Handle events
PAYMENT_HANDLE_INITIATED— Handle created, redirect pending.PAYMENT_HANDLE_PROCESSING— Customer redirected (3DS/APM).PAYMENT_HANDLE_PAYABLE— Handle ready for payment call.PAYMENT_HANDLE_COMPLETED— Payment submitted successfully.PAYMENT_HANDLE_FAILED— Handle or transaction failed.PAYMENT_HANDLE_EXPIRED— Handle TTL elapsed unused.PAYMENT_HANDLE_CANCELLED— Handle cancelled.
Payment events
PAYMENT_COMPLETEDPAYMENT_FAILEDPAYMENT_PENDING
Settlement events
SETTLEMENT_COMPLETEDSETTLEMENT_FAILEDSETTLEMENT_CANCELLED
Refund events
REFUND_COMPLETEDREFUND_FAILEDREFUND_CANCELLED
Scheduler events
SUBSCRIPTION_PAYMENT_SCHEDULEDSUBSCRIPTION_PAYMENT_COMPLETEDSUBSCRIPTION_PAYMENT_FAILEDSUBSCRIPTION_PAYMENT_RETRYSUBSCRIPTION_SUSPENDEDSUBSCRIPTION_CANCELLEDSUBSCRIPTION_TRIAL_COMPLETEDSUBSCRIPTION_COMPLETED
Account Updater events
ACCOUNT_UPDATER_BATCH_COMPLETED
Example — Phoenix controller
def webhook(conn, _params) do
signature = get_req_header(conn, "signature") |> List.first()
raw_body = conn.assigns[:raw_body] # Must capture before parsing
case Paysafe.Webhooks.verify_and_parse(raw_body, signature, hmac_key) do
{:ok, event} ->
handle_event(event)
send_resp(conn, 200, "ok")
{:error, %Paysafe.Error{kind: :webhook_signature_mismatch}} ->
send_resp(conn, 401, "invalid signature")
{:error, _} ->
send_resp(conn, 400, "bad request")
end
end
Summary
Functions
Return the topic category of a webhook event.
Parse a webhook body that has already been verified.
Parse an already-decoded map into a WebhookEvent struct.
Extract the payment handle status from a payment handle webhook event.
Verify the HMAC-SHA256 signature and decode the webhook payload.
Verify the webhook signature without parsing the body.
Functions
@spec event_topic(Paysafe.Types.WebhookEvent.t()) :: atom()
Return the topic category of a webhook event.
Useful for routing events to handlers without pattern matching on raw strings.
Returns
:payment_handle— Payment handle lifecycle events.:payment— Payment authorisation/capture events.:settlement— Settlement events.:refund— Refund events.:payout— Standalone credit/original credit events.:subscription— Scheduler subscription events.:account_updater— Account updater batch events.:unknown— Unrecognised event type.
@spec parse(binary()) :: {:ok, Paysafe.Types.WebhookEvent.t()} | {:error, Paysafe.Error.t()}
Parse a webhook body that has already been verified.
Use this when you handle verification separately (e.g. in a plug).
@spec parse_map(map()) :: {:ok, Paysafe.Types.WebhookEvent.t()} | {:error, Paysafe.Error.t()}
Parse an already-decoded map into a WebhookEvent struct.
@spec payment_handle_status(Paysafe.Types.WebhookEvent.t()) :: atom() | nil
Extract the payment handle status from a payment handle webhook event.
Returns nil for non-payment-handle events.
@spec verify_and_parse(binary(), String.t(), String.t()) :: {:ok, Paysafe.Types.WebhookEvent.t()} | {:error, Paysafe.Error.t()}
Verify the HMAC-SHA256 signature and decode the webhook payload.
Parameters
raw_body— The raw (unmodified) request body binary.signature— The value from theSignatureHTTP header.hmac_key— Your webhook HMAC key (obtained from Paysafe support/portal).
Returns {:ok, %WebhookEvent{}} or {:error, %Error{}}.
Notes
Uses constant-time comparison (Plug.Crypto.secure_compare/2-style) to
prevent timing attacks.
@spec verify_signature(binary(), String.t(), String.t()) :: :ok | {:error, Paysafe.Error.t()}
Verify the webhook signature without parsing the body.
Returns :ok or {:error, %Error{kind: :webhook_signature_mismatch}}.