Webhook Events
Complete reference of all webhook events and their payloads
Event Types
Payzo sends webhooks for these events:
| Event | Description | When It's Sent |
|---|---|---|
payment.completed | Payment successful | Customer successfully paid |
payment.failed | Payment failed | Card declined or payment error |
payment.refunded | Payment refunded | Admin issued a refund |
payment.expired | Payment expired | Checkout link expired unused |
Event: payment.completed
Sent when a customer successfully completes a payment.
When to Handle
Handle this event to:
- Mark orders as paid
- Deliver digital goods
- Send confirmation emails
- Update inventory
- Trigger fulfillment
Payload Example
{
"event": "payment.completed",
"payment": {
"id": "pay_abc123def456",
"amount": 50.00,
"currency": "usd",
"status": "completed",
"customer_email": "customer@example.com",
"customer_name": "John Doe",
"metadata": {
"order_id": "ORD-12345",
"user_id": "user_789",
"product_name": "Premium Package"
},
"created_at": "2025-01-12T10:30:00.000Z",
"completed_at": "2025-01-12T10:30:15.000Z"
},
"shop": {
"id": "shop_abc123",
"shop_name": "My Awesome Store"
},
"timestamp": "2025-01-12T10:30:15.000Z"
}
Example Handler
async function handlePaymentCompleted(payment) {
const orderId = payment.metadata.order_id;
// 1. Update order status
await db.orders.update({
id: orderId,
status: 'paid',
paid_at: payment.completed_at,
payment_id: payment.id
});
// 2. Deliver product
await deliverProduct(orderId);
// 3. Send confirmation
await sendEmail(payment.customer_email, {
subject: 'Payment Confirmed',
template: 'payment-success',
data: {
order_id: orderId,
amount: payment.amount
}
});
// 4. Update inventory
await updateInventory(payment.metadata.product_id, -1);
console.log(`Order ${orderId} completed successfully`);
}
Event: payment.failed
Sent when a payment attempt fails (card declined, insufficient funds, etc.).
When to Handle
Handle this event to:
- Mark orders as failed
- Send payment failure notification
- Prompt customer to retry
- Log failed attempts
Payload Example
{
"event": "payment.failed",
"payment": {
"id": "pay_failed123",
"amount": 25.00,
"currency": "usd",
"status": "failed",
"customer_email": "customer@example.com",
"customer_name": null,
"metadata": {
"order_id": "ORD-67890"
},
"created_at": "2025-01-12T11:00:00.000Z",
"completed_at": "2025-01-12T11:00:05.000Z"
},
"shop": {
"id": "shop_abc123",
"shop_name": "My Awesome Store"
},
"timestamp": "2025-01-12T11:00:05.000Z"
}
Example Handler
async function handlePaymentFailed(payment) {
const orderId = payment.metadata.order_id;
// 1. Update order status
await db.orders.update({
id: orderId,
status: 'payment_failed',
failed_at: payment.completed_at
});
// 2. Send failure notification
await sendEmail(payment.customer_email, {
subject: 'Payment Failed',
template: 'payment-failed',
data: {
order_id: orderId,
reason: 'Your card was declined. Please try a different payment method.'
}
});
// 3. Log the failure
await db.payment_logs.create({
order_id: orderId,
event: 'payment_failed',
payment_id: payment.id
});
console.log(`Payment failed for order ${orderId}`);
}
Event: payment.refunded
Sent when a refund is issued by Payzo support.
Refunds can be requested by:
When a refund is approved, the refund amount is automatically deducted from your seller balance (no additional fees). You'll receive this webhook to notify your system.
When to Handle
Handle this event to:
- Reverse order fulfillment (e.g., revoke game items)
- Update order status to refunded
- Send refund confirmation to customer
- Restore inventory
- Update your accounting records
Payload Example
{
"event": "payment.refunded",
"payment": {
"id": "pay_abc123def456",
"amount": 100.00,
"currency": "usd",
"status": "refunded",
"customer_email": "customer@example.com",
"customer_name": "Jane Smith",
"metadata": {
"order_id": "ORD-11111",
"refund_reason": "Customer request"
},
"created_at": "2025-01-10T09:00:00.000Z",
"completed_at": "2025-01-10T09:05:00.000Z"
},
"shop": {
"id": "shop_abc123",
"shop_name": "My Awesome Store"
},
"timestamp": "2025-01-12T14:00:00.000Z"
}
Example Handler
async function handlePaymentRefunded(payment) {
const orderId = payment.metadata.order_id;
// 1. Update order status
await db.orders.update({
id: orderId,
status: 'refunded',
refunded_at: new Date()
});
// 2. Reverse fulfillment
await revokeAccess(orderId);
// 3. Restore inventory
await updateInventory(payment.metadata.product_id, +1);
// 4. Send refund notification
await sendEmail(payment.customer_email, {
subject: 'Refund Processed',
template: 'refund-confirmation',
data: {
order_id: orderId,
amount: payment.amount,
refund_date: new Date()
}
});
console.log(`Order ${orderId} refunded successfully`);
}
Event: payment.expired
Sent when a checkout link expires without being completed (rare).
When to Handle
Optional to handle:
- Clean up pending orders
- Log abandoned checkouts
- Track conversion rates
Payload Example
{
"event": "payment.expired",
"payment": {
"id": "pay_expired123",
"amount": 15.00,
"currency": "usd",
"status": "expired",
"customer_email": null,
"customer_name": null,
"metadata": {
"order_id": "ORD-99999"
},
"created_at": "2025-01-11T10:00:00.000Z",
"completed_at": null
},
"shop": {
"id": "shop_abc123",
"shop_name": "My Awesome Store"
},
"timestamp": "2025-01-12T10:00:00.000Z"
}
Example Handler
async function handlePaymentExpired(payment) {
const orderId = payment.metadata.order_id;
// 1. Mark as expired
await db.orders.update({
id: orderId,
status: 'expired'
});
// 2. Log for analytics
await db.analytics.log({
event: 'checkout_abandoned',
order_id: orderId,
amount: payment.amount
});
console.log(`Payment expired for order ${orderId}`);
}
Complete Webhook Handler
Node.js Example
async function processWebhook(data) {
const { event, payment } = data;
// Route to appropriate handler
switch (event) {
case 'payment.completed':
await handlePaymentCompleted(payment);
break;
case 'payment.failed':
await handlePaymentFailed(payment);
break;
case 'payment.refunded':
await handlePaymentRefunded(payment);
break;
case 'payment.expired':
await handlePaymentExpired(payment);
break;
default:
console.warn('Unknown webhook event:', event);
}
}
Event Flow Diagram
Customer initiates payment
↓
[payment.pending]
↓
┌────────────┐
│ Card Check │
└────┬───────┘
│
┌────┴────┐
│ │
SUCCESS DECLINED
│ │
↓ ↓
payment. payment.
completed failed
│
│ Refund requested?
├──────────→ payment.refunded
│
│ Expires?
└──────────→ payment.expired
Idempotency
Always check if you've already processed a webhook:
async function handlePaymentCompleted(payment) {
// Check if already processed
const existing = await db.payments.findOne({
payzo_payment_id: payment.id,
status: 'completed'
});
if (existing) {
console.log('Payment already processed, skipping');
return; // Don't process twice
}
// Process payment...
}
Testing Events
Using Dashboard
- Go to Dashboard > Shops
- Select your shop
- Click "Test Webhook"
- Sends
payment.completedtest event
Manual Testing
curl -X POST http://localhost:3000/webhook/payzo \
-H "Content-Type: application/json" \
-H "X-Payzo-Event: payment.completed" \
-H "X-Payzo-Signature: your_signature_here" \
-d '{
"event": "payment.completed",
"payment": {
"id": "test_123",
"amount": 10.00,
"currency": "usd",
"status": "completed",
"metadata": {"order_id": "TEST-001"}
}
}'
Event Metadata
Use metadata to pass custom data through the payment flow:
// When creating payment
const payment = await createPayment({
amount: 5000,
metadata: {
order_id: 'ORD-12345',
user_id: 'user_789',
product_id: 'prod_456',
discount_code: 'SAVE20',
affiliate_id: 'aff_123'
}
});
// Metadata is returned in webhooks
function handlePaymentCompleted(payment) {
const {
order_id,
user_id,
product_id,
discount_code,
affiliate_id
} = payment.metadata;
// Use metadata to process order
}
Error Handling
async function processWebhook(data) {
try {
const { event, payment } = data;
if (event === 'payment.completed') {
await handlePaymentCompleted(payment);
}
// ... other events
} catch (error) {
// Log error but don't throw
console.error('Webhook processing error:', error);
// Save to error log
await db.webhook_errors.create({
event: data.event,
payment_id: data.payment.id,
error: error.message,
stack: error.stack,
timestamp: new Date()
});
// Alert team for critical errors
if (isCriticalError(error)) {
await alertTeam('Webhook processing failed', error);
}
}
}
Next Steps
- Webhooks Overview - Complete webhook guide
- Webhook Verification - Secure your webhooks
- API Reference - Create payments