Kyren Pay sends webhook notifications for the following event types.
Event Types
| Event | Description | Trigger |
|---|
order.paid | A payment has been confirmed | Customer completes checkout |
order.refunded | A refund has been applied to an order | A refund succeeds |
order.closed | An order has been closed without successful payment | Payment is canceled, fails, times out, authorization is released, or the gateway closes the order |
Event Object Structure
All webhook events share the same top-level structure:
{
"id": "evt_abc123",
"type": "order.paid",
"created_at": 1736932500000,
"data": {
// Event-specific data
}
}
| Field | Type | Description |
|---|
id | string | Unique event identifier (use for deduplication) |
type | string | The event type |
created_at | integer | Unix timestamp in milliseconds |
data | object | Event-specific payload |
order.paid
Sent when a customer successfully completes a payment.
{
"id": "evt_abc123",
"type": "order.paid",
"created_at": 1736932500000,
"data": {
"order_id": "order_def456",
"product_id": "prod_abc123",
"customer_email": "customer@example.com",
"amount": "9.99",
"currency": "USD",
"net_amount": "9.29",
"paid_at": 1736932500000,
"metadata": { "user_id": "u_123" }
}
}
| Field | Type | Description |
|---|
order_id | string | The order ID |
product_id | string | The product that was purchased |
customer_email | string | Customer’s email address |
amount | string | Total payment amount |
currency | string | Three-letter currency code |
net_amount | string | Amount after fees |
paid_at | integer | When the payment was confirmed (Unix timestamp in milliseconds) |
metadata | object | null | The metadata you passed when creating the checkout session |
This is the most important event. Use it to fulfill orders — for example, adding credits to a user’s account.
order.refunded
Sent when a refund has been applied to an order. Use this event to update entitlement, balance, or reconciliation records in your system.
See Order refunds for the dashboard refund request flow and refund status definitions.
{
"id": "evt_refund123",
"type": "order.refunded",
"created_at": 1736932600000,
"data": {
"order_id": "order_def456",
"refund_id": "refund_abc123",
"refund_status": "PARTIAL",
"amount": "2.50",
"currency": "USD",
"refunded_amount": "2.50",
"original_amount": "9.99",
"reason": "customer_request",
"metadata": { "user_id": "u_123" }
}
}
| Field | Type | Description |
|---|
order_id | string | The order ID |
refund_id | string | The refund ID |
refund_status | string | Order refund status, such as PARTIAL or FULL |
amount | string | Amount refunded by this event |
currency | string | Three-letter currency code |
refunded_amount | string | Total refunded amount on the order after this refund |
original_amount | string | Original order amount |
reason | string | null | Refund reason |
metadata | object | null | The metadata you passed when creating the checkout session |
order.closed
Sent when an order is closed without successful payment.
{
"id": "evt_closed123",
"type": "order.closed",
"created_at": 1736932700000,
"data": {
"order_id": "order_def456",
"product_id": "prod_abc123",
"customer_email": "customer@example.com",
"amount": "9.99",
"currency": "USD",
"closed_at": 1736932700000,
"close_reason": "payment_timeout",
"compat": { "epay_trade_status": "TRADE_CLOSED" },
"metadata": { "user_id": "u_123" }
}
}
| Field | Type | Description |
|---|
order_id | string | The order ID |
product_id | string | The product that was being purchased |
customer_email | string | Customer’s email address |
amount | string | Total order amount |
currency | string | Three-letter currency code |
closed_at | integer | When the order was closed (Unix timestamp in milliseconds) |
close_reason | string | Closure reason, such as payment_canceled, payment_failed, payment_timeout, authorization_released, or gateway_closed |
compat | object | Compatibility fields for legacy processors |
metadata | object | null | The metadata you passed when creating the checkout session |
Handling Events
Here’s how to handle webhook events in your endpoint:
app.post('/webhooks/kyren', (req, res) => {
// ... verify signature first ...
const event = JSON.parse(req.body.toString());
switch (event.type) {
case 'order.paid':
handleOrderPaid(event.data);
break;
case 'order.refunded':
handleOrderRefunded(event.data);
break;
case 'order.closed':
handleOrderClosed(event.data);
break;
default:
console.log(`Unhandled event: ${event.type}`);
}
res.status(200).send('OK');
});