Skip to main content
Polling the API for payment status is unreliable — network timeouts, user drop-offs, and asynchronous bank processing all mean that a transaction may settle seconds or minutes after the customer leaves your checkout page. Webhooks solve this by having Paylink push a signed notification to your server the moment a transaction status changes. Your backend receives the event, verifies its authenticity, and updates your records without ever needing to poll.

Configuring a Webhook URL

Pass a webhook_url in the request body when you initialise a payment, create a virtual account, set up a direct debit, or initiate a transfer. Paylink will POST event notifications to that URL as the transaction progresses.
Your webhook endpoint must be publicly reachable over HTTPS and must respond with a 2xx status code to acknowledge receipt. If your endpoint returns a non-2xx response or does not reply in time, Paylink retries the delivery with exponential back-off.

Delivery Headers

Every webhook request includes the following HTTP headers.

Verifying Signatures

Paylink signs every webhook using HMAC-SHA256. The signature is computed as:
Where secret is the webhook secret associated with your transaction, timestamp is the value from the X-Payment-Timestamp header, and compact_json_payload is the raw request body exactly as received — not re-serialised.
Always verify the signature against the raw request body bytes delivered by Paylink. Re-serialising the parsed JSON before hashing will produce a different string and cause every verification to fail. In most frameworks, you can access the raw body through a dedicated buffer or by reading the request stream before your JSON parser processes it.
The following Python snippet shows a complete verification helper.
Reject any request where verification fails or where the timestamp is significantly older than the current time (for example, more than five minutes) to guard against replay attacks.

Payload Envelope

Every event shares the same top-level envelope structure. The data object contains event-specific fields described in the Event Payloads section below.

Event Types

Event Payloads

The data object varies by event type. Expand each section below to see the full payload.
Sent when a card or hosted-checkout payment is processed. merchant_bears_fees indicates whether the fee was absorbed by you or passed to the payer.
Sent when a customer transfers funds to a virtual account. The account_reference in meta identifies the virtual account that received the transfer.
Sent when Lyseis Pay attempts to collect funds under an active mandate. The mandate_code ties the debit back to the mandate created during setup.
Sent once when a direct debit mandate transitions to ACTIVE status, confirming that you can begin initiating debits against it.
Sent when a bank transfer (payout) to a recipient account changes status. A disbursement_reversal means a previously settled transfer was reversed by the receiving bank.

Best Practices

Ensure idempotency

Record the event_id from every incoming webhook before processing it. If a delivery is retried and you receive the same event_id again, skip processing and return 200 OK immediately. This prevents double-crediting, duplicate fulfilment, or other unintended side-effects.

Return 2xx quickly

Acknowledge the webhook with a 200 OK (or any 2xx) as fast as possible — ideally before you do any heavy processing. Enqueue the event for background processing if needed. Slow or hanging handlers will be treated as failed deliveries and retried.

Expect retries with back-off

Paylink retries failed deliveries with exponential back-off. Your handler must tolerate receiving the same event more than once. Idempotency keying on event_id is the correct way to handle this safely.

Reject stale or invalid signatures

Always verify the X-Payment-Signature header and reject events with signatures that do not match. Also reject events where X-Payment-Timestamp is more than a few minutes in the past to mitigate replay attacks.