Paysafe.Webhooks (Paysafe v1.0.0)

Copy Markdown View Source

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_COMPLETED
  • PAYMENT_FAILED
  • PAYMENT_PENDING

Settlement events

  • SETTLEMENT_COMPLETED
  • SETTLEMENT_FAILED
  • SETTLEMENT_CANCELLED

Refund events

  • REFUND_COMPLETED
  • REFUND_FAILED
  • REFUND_CANCELLED

Scheduler events

  • SUBSCRIPTION_PAYMENT_SCHEDULED
  • SUBSCRIPTION_PAYMENT_COMPLETED
  • SUBSCRIPTION_PAYMENT_FAILED
  • SUBSCRIPTION_PAYMENT_RETRY
  • SUBSCRIPTION_SUSPENDED
  • SUBSCRIPTION_CANCELLED
  • SUBSCRIPTION_TRIAL_COMPLETED
  • SUBSCRIPTION_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

event_topic(webhook_event)

@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.

parse(raw_body)

@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).

parse_map(map)

@spec parse_map(map()) ::
  {:ok, Paysafe.Types.WebhookEvent.t()} | {:error, Paysafe.Error.t()}

Parse an already-decoded map into a WebhookEvent struct.

payment_handle_status(webhook_event)

@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.

verify_and_parse(raw_body, signature, hmac_key)

@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 the Signature HTTP 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.

verify_signature(raw_body, signature, hmac_key)

@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}}.