Webhook Events Reference
Complete reference of all webhook event types and payload structures.
Complete reference of all webhook events sent by Paysera Checkout.
Event Types​
The event.name field identifies the trigger. Route on this value (not on headers).
| Event | Type | Description |
|---|---|---|
order.created | order | An order was created |
order.status_updated | order | Order status changed (most commonly to paid) |
order.reference_updated | order | Merchant reference for the order changed |
order.amount_updated | order | Order total amount changed |
order.amount_paid_updated | order | Order's amount_paid increased (partial or full payment) |
order.payment_link.expired_at_updated | order | A linked payment link's expired_at changed |
Additional internal payment-link sub-events exist (created, name/amount/language/receiver/payer/payment-method/status updates). They may surface in the future — treat unknown event.name values as no-ops rather than failing.
- All timestamps are Unix epoch (seconds, UTC)
- All amounts are in minor currency units (e.g., cents)
- All payload fields are snake_case
Webhook Payload Structure​
Common Shape​
Every webhook delivers the full order snapshot at the moment the event fired. Top-level keys are event and order. Payment links and their payment attempts appear nested under order.payment_links[] and order.payment_links[].payments[].
{
"event": {
"name": "order.status_updated",
"type": "order"
},
"order": {
"paysera_order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"merchant_order_id": "ORDER-12345",
"source": "https://myshop.paysera.net",
"amount": 2500,
"amount_paid": 2500,
"currency": "EUR",
"status": "paid",
"created_at": 1736433270,
"updated_at": 1736433570,
"merchant_data": [
{ "key": "internal_id", "value": "12345" }
],
"payment_links": [
{
"id": "c8d9e0f1-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
"name": "Order #12345",
"created_at": 1736433270,
"updated_at": 1736433570,
"payer_name": "John Doe",
"payer_email": "john.doe@paysera.net",
"payments": [
{
"id": "p-1",
"method": "swedbank",
"status": "settled",
"original_amount": null,
"original_currency": null,
"payment_currency": "EUR",
"payment_amount": 2500,
"updated_at": 1736433570,
"payer_name": "John Doe",
"payer_email": "john.doe@paysera.net",
"payment_country": "LT",
"payer_ip_country": "LT",
"payer_country": "LT",
"purpose": "Order #12345"
}
]
}
]
}
}
Event Details​
order.created​
Sent when an order is created.
{
"event": { "name": "order.created", "type": "order" },
"order": {
"paysera_order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"merchant_order_id": "ORDER-12345",
"source": "https://myshop.paysera.net",
"amount": 2500,
"amount_paid": 0,
"currency": "EUR",
"status": "pending_payment",
"created_at": 1736433270,
"updated_at": 1736433270,
"merchant_data": [],
"payment_links": []
}
}
Actions to take:
- Record the order in your system if not already known
- No payment has occurred yet — do not fulfill
order.status_updated​
Sent when the order's status changes. The most common transition you fulfill on is to paid. Always branch on order.status, not on the event name alone.
{
"event": { "name": "order.status_updated", "type": "order" },
"order": {
"paysera_order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"merchant_order_id": "ORDER-12345",
"amount": 2500,
"amount_paid": 2500,
"currency": "EUR",
"status": "paid",
"created_at": 1736433270,
"updated_at": 1736433570,
"payment_links": [
{
"id": "c8d9e0f1-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
"name": "Order #12345",
"payments": [
{ "id": "p-1", "method": "swedbank", "status": "settled",
"payment_currency": "EUR", "payment_amount": 2500 }
]
}
]
}
}
Actions to take when order.status == "paid":
- Mark the order as paid in your system
- Send confirmation email
- Trigger fulfillment
order.amount_paid_updated​
Sent when the cumulative amount paid on the order increases (e.g., partial settlement). For fully-paid orders, expect both this event and order.status_updated (status → paid).
{
"event": { "name": "order.amount_paid_updated", "type": "order" },
"order": {
"paysera_order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"merchant_order_id": "ORDER-12345",
"amount": 2500,
"amount_paid": 1500,
"currency": "EUR",
"status": "pending_payment",
"created_at": 1736433270,
"updated_at": 1736433400,
"payment_links": [
{
"id": "c8d9e0f1-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
"name": "Order #12345",
"payments": [
{ "id": "p-1", "method": "swedbank", "status": "settled",
"payment_currency": "EUR", "payment_amount": 1500 }
]
}
]
}
}
Actions to take:
- Reconcile partial payment against your order
- Do not fulfill unless
amount_paid == amount
order.amount_updated​
Sent when the order's total amount changes (e.g., merchant-initiated update).
{
"event": { "name": "order.amount_updated", "type": "order" },
"order": {
"paysera_order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"merchant_order_id": "ORDER-12345",
"amount": 3000,
"amount_paid": 0,
"currency": "EUR",
"status": "pending_payment",
"created_at": 1736433270,
"updated_at": 1736433500
}
}
Actions to take:
- Update the recorded total in your system
order.reference_updated​
Sent when the merchant_order_id (reference) is changed for the order.
{
"event": { "name": "order.reference_updated", "type": "order" },
"order": {
"paysera_order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"merchant_order_id": "ORDER-NEW-12345",
"amount": 2500,
"amount_paid": 0,
"currency": "EUR",
"status": "pending_payment",
"created_at": 1736433270,
"updated_at": 1736433500
}
}
Actions to take:
- Update your local reference mapping (look up by
paysera_order_id, which is stable)
order.payment_link.expired_at_updated​
Sent when the expired_at of a payment link associated with the order is changed.
{
"event": { "name": "order.payment_link.expired_at_updated", "type": "order" },
"order": {
"paysera_order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"merchant_order_id": "ORDER-12345",
"amount": 2500,
"amount_paid": 0,
"currency": "EUR",
"status": "pending_payment",
"created_at": 1736433270,
"updated_at": 1736433600,
"payment_links": [
{
"id": "c8d9e0f1-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
"name": "Order #12345",
"created_at": 1736433270,
"updated_at": 1736433600
}
]
}
}
Actions to take:
- Refresh your cached link expiry, if you display it to customers
Webhook Headers​
| Header | Description |
|---|---|
Content-Type | application/json |
X-Paysera-Signature | Hex-encoded HMAC of the raw body |
X-Paysera-Signature-Alg | HMAC-SHA256 |
X-Paysera-Created-At | Unix timestamp (seconds, UTC) of webhook generation |
X-Paysera-Request-Id | Unique request identifier (for tracing) |
X-Paysera-Callback-Id | Unique callback identifier (use for idempotency) |
X-Paysera-Event headerThe event name is in the JSON body at event.name. There is no X-Paysera-Event HTTP header — do not branch on headers for the event name.
Complete Handler Example​
PHP​
<?php
class WebhookHandler
{
private string $secret; // Project webhook secret, NOT OAuth client_secret
private OrderRepository $orders;
public function handle(): void
{
// Read raw body and headers
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PAYSERA_SIGNATURE'] ?? '';
$callbackId = $_SERVER['HTTP_X_PAYSERA_CALLBACK_ID'] ?? '';
// Verify signature
if (!$this->verifySignature($payload, $signature)) {
http_response_code(401);
return;
}
// Idempotency check — skip if this callback ID was already processed
if ($this->orders->callbackProcessed($callbackId)) {
http_response_code(200);
return;
}
// Parse payload
$data = json_decode($payload, true);
$eventName = $data['event']['name'] ?? null;
$order = $data['order'] ?? null;
$merchantOrderId = $order['merchant_order_id'] ?? null;
$payseraOrderId = $order['paysera_order_id'] ?? null;
$status = $order['status'] ?? null;
// Branch on event name; ignore unknown values
match ($eventName) {
'order.created' => $this->handleOrderCreated($payseraOrderId, $order),
'order.status_updated' => $this->handleStatusUpdated($payseraOrderId, $status, $order),
'order.amount_paid_updated' => $this->handleAmountPaidUpdated($payseraOrderId, $order),
'order.amount_updated' => $this->handleAmountUpdated($payseraOrderId, $order),
'order.reference_updated' => $this->handleReferenceUpdated($payseraOrderId, $merchantOrderId),
'order.payment_link.expired_at_updated' => $this->handleLinkExpiryUpdated($payseraOrderId, $order),
default => error_log("Unknown webhook event: $eventName"),
};
$this->orders->markCallbackProcessed($callbackId);
http_response_code(200);
echo 'OK';
}
private function verifySignature(string $payload, string $signature): bool
{
$expected = hash_hmac('sha256', $payload, $this->secret);
return hash_equals($expected, $signature);
}
private function handleOrderCreated(string $payseraOrderId, array $order): void
{
// Persist Paysera ↔ merchant order mapping
}
private function handleStatusUpdated(string $payseraOrderId, string $status, array $order): void
{
if ($status === 'paid') {
$this->orders->markPaid($payseraOrderId);
// Trigger fulfillment, send confirmation email, etc.
}
}
private function handleAmountPaidUpdated(string $payseraOrderId, array $order): void
{
// Reconcile partial payment
}
private function handleAmountUpdated(string $payseraOrderId, array $order): void
{
// Update stored order total
}
private function handleReferenceUpdated(string $payseraOrderId, string $merchantOrderId): void
{
// Update local mapping
}
private function handleLinkExpiryUpdated(string $payseraOrderId, array $order): void
{
// Update cached expiry
}
}