Skip to content

Webhook Event Structure

Webhook Event Structure

This guide documents the exact structure of all webhook events emitted by QZPay. Understanding these structures is essential for building robust webhook handlers.

Event Structure

All QZPay events follow this base structure:

interface QZPayEvent<T> {
/** Unique event identifier */
id: string;
/** Event type (e.g., 'customer.created') */
type: QZPayBillingEvent;
/** Event-specific data payload */
data: T;
/** Whether this is a live mode event */
livemode: boolean;
/** When the event was created */
createdAt: Date;
}

Event Types

Customer Events

customer.created

Emitted when a new customer is created.

{
id: "evt_abc123",
type: "customer.created",
data: {
id: "cus_abc123",
externalId: "user_789",
email: "customer@example.com",
name: "John Doe",
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

customer.updated

Emitted when customer information is updated.

{
id: "evt_abc124",
type: "customer.updated",
data: {
id: "cus_abc123",
externalId: "user_789",
email: "newemail@example.com", // Updated field
name: "John Doe",
metadata: { tier: "premium" }, // Updated metadata
createdAt: Date,
updatedAt: Date // New timestamp
},
livemode: true,
createdAt: Date
}

customer.deleted

Emitted when a customer is deleted.

{
id: "evt_abc125",
type: "customer.deleted",
data: {
id: "cus_abc123",
externalId: "user_789",
email: "customer@example.com",
name: "John Doe",
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

Subscription Events

subscription.created

Emitted when a new subscription is created.

{
id: "evt_sub001",
type: "subscription.created",
data: {
id: "sub_abc123",
customerId: "cus_abc123",
planId: "plan_pro",
priceId: "price_pro_monthly",
status: "active",
quantity: 1,
interval: "month",
intervalCount: 1,
currentPeriodStart: Date,
currentPeriodEnd: Date,
trialStart: null,
trialEnd: null,
canceledAt: null,
cancelAtPeriodEnd: false,
pausedAt: null,
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

subscription.updated

Emitted when subscription details change (plan, quantity, etc.).

{
id: "evt_sub002",
type: "subscription.updated",
data: {
id: "sub_abc123",
customerId: "cus_abc123",
planId: "plan_enterprise", // Changed from plan_pro
priceId: "price_enterprise_monthly",
status: "active",
quantity: 2, // Increased from 1
interval: "month",
intervalCount: 1,
currentPeriodStart: Date,
currentPeriodEnd: Date,
trialStart: null,
trialEnd: null,
canceledAt: null,
cancelAtPeriodEnd: false,
pausedAt: null,
metadata: { upgraded: true },
createdAt: Date,
updatedAt: Date // Updated timestamp
},
livemode: true,
createdAt: Date
}

subscription.canceled

Emitted when a subscription is canceled.

{
id: "evt_sub003",
type: "subscription.canceled",
data: {
id: "sub_abc123",
customerId: "cus_abc123",
planId: "plan_pro",
priceId: "price_pro_monthly",
status: "canceled",
quantity: 1,
interval: "month",
intervalCount: 1,
currentPeriodStart: Date,
currentPeriodEnd: Date,
trialStart: null,
trialEnd: null,
canceledAt: Date, // Set when canceled
cancelAtPeriodEnd: false,
pausedAt: null,
metadata: {
cancellationReason: "switching_provider"
},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

subscription.paused

Emitted when a subscription is paused.

{
id: "evt_sub004",
type: "subscription.paused",
data: {
id: "sub_abc123",
customerId: "cus_abc123",
planId: "plan_pro",
priceId: "price_pro_monthly",
status: "paused",
quantity: 1,
interval: "month",
intervalCount: 1,
currentPeriodStart: Date,
currentPeriodEnd: Date,
trialStart: null,
trialEnd: null,
canceledAt: null,
cancelAtPeriodEnd: false,
pausedAt: Date, // Set when paused
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

subscription.resumed

Emitted when a paused subscription is resumed.

{
id: "evt_sub005",
type: "subscription.resumed",
data: {
id: "sub_abc123",
customerId: "cus_abc123",
planId: "plan_pro",
priceId: "price_pro_monthly",
status: "active",
quantity: 1,
interval: "month",
intervalCount: 1,
currentPeriodStart: Date,
currentPeriodEnd: Date,
trialStart: null,
trialEnd: null,
canceledAt: null,
cancelAtPeriodEnd: false,
pausedAt: null, // Cleared when resumed
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

subscription.trial_ending

Emitted 3 days before trial ends (configurable).

{
id: "evt_sub006",
type: "subscription.trial_ending",
data: {
id: "sub_abc123",
customerId: "cus_abc123",
planId: "plan_pro",
priceId: "price_pro_monthly",
status: "trialing",
quantity: 1,
interval: "month",
intervalCount: 1,
currentPeriodStart: Date,
currentPeriodEnd: Date,
trialStart: Date,
trialEnd: Date, // 3 days from now
canceledAt: null,
cancelAtPeriodEnd: false,
pausedAt: null,
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

subscription.trial_ended

Emitted when trial period ends.

{
id: "evt_sub007",
type: "subscription.trial_ended",
data: {
id: "sub_abc123",
customerId: "cus_abc123",
planId: "plan_pro",
priceId: "price_pro_monthly",
status: "active", // Converted from trialing
quantity: 1,
interval: "month",
intervalCount: 1,
currentPeriodStart: Date,
currentPeriodEnd: Date,
trialStart: Date,
trialEnd: Date, // Just ended
canceledAt: null,
cancelAtPeriodEnd: false,
pausedAt: null,
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

Payment Events

payment.succeeded

Emitted when a payment is successful.

{
id: "evt_pay001",
type: "payment.succeeded",
data: {
id: "pay_abc123",
customerId: "cus_abc123",
amount: 9900, // $99.00 in cents
currency: "USD",
status: "succeeded",
provider: "stripe",
providerPaymentId: "pi_stripe123",
invoiceId: "inv_abc123",
subscriptionId: "sub_abc123",
failureReason: null,
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

payment.failed

Emitted when a payment fails.

{
id: "evt_pay002",
type: "payment.failed",
data: {
id: "pay_abc124",
customerId: "cus_abc123",
amount: 9900,
currency: "USD",
status: "failed",
provider: "stripe",
providerPaymentId: "pi_stripe124",
invoiceId: "inv_abc124",
subscriptionId: "sub_abc123",
failureReason: "card_declined",
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

payment.refunded

Emitted when a payment is refunded.

{
id: "evt_pay003",
type: "payment.refunded",
data: {
id: "pay_abc123",
customerId: "cus_abc123",
amount: 9900,
currency: "USD",
status: "refunded",
provider: "stripe",
providerPaymentId: "pi_stripe123",
invoiceId: "inv_abc123",
subscriptionId: "sub_abc123",
failureReason: null,
metadata: {
refundReason: "customer_request",
refundedAt: Date
},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

payment.disputed

Emitted when a payment is disputed (chargeback).

{
id: "evt_pay004",
type: "payment.disputed",
data: {
id: "pay_abc123",
customerId: "cus_abc123",
amount: 9900,
currency: "USD",
status: "disputed",
provider: "stripe",
providerPaymentId: "pi_stripe123",
invoiceId: "inv_abc123",
subscriptionId: "sub_abc123",
failureReason: null,
metadata: {
disputeReason: "fraudulent",
disputeId: "dp_stripe123"
},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

Invoice Events

invoice.created

Emitted when an invoice is created.

{
id: "evt_inv001",
type: "invoice.created",
data: {
id: "inv_abc123",
customerId: "cus_abc123",
subscriptionId: "sub_abc123",
amount: 9900,
currency: "USD",
status: "open",
dueDate: Date,
paidAt: null,
voidedAt: null,
lines: [
{
description: "Pro Plan - Monthly",
quantity: 1,
unitAmount: 9900,
priceId: "price_pro_monthly"
}
],
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

invoice.paid

Emitted when an invoice is paid.

{
id: "evt_inv002",
type: "invoice.paid",
data: {
id: "inv_abc123",
customerId: "cus_abc123",
subscriptionId: "sub_abc123",
amount: 9900,
currency: "USD",
status: "paid",
dueDate: Date,
paidAt: Date, // Set when paid
voidedAt: null,
lines: [...],
metadata: {
paymentId: "pay_abc123"
},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

invoice.payment_failed

Emitted when invoice payment fails.

{
id: "evt_inv003",
type: "invoice.payment_failed",
data: {
id: "inv_abc124",
customerId: "cus_abc123",
subscriptionId: "sub_abc123",
amount: 9900,
currency: "USD",
status: "open",
dueDate: Date,
paidAt: null,
voidedAt: null,
lines: [...],
metadata: {
paymentAttempts: 1,
lastPaymentError: "card_declined"
},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

invoice.voided

Emitted when an invoice is voided.

{
id: "evt_inv004",
type: "invoice.voided",
data: {
id: "inv_abc125",
customerId: "cus_abc123",
subscriptionId: "sub_abc123",
amount: 9900,
currency: "USD",
status: "voided",
dueDate: Date,
paidAt: null,
voidedAt: Date, // Set when voided
lines: [...],
metadata: {
voidReason: "duplicate"
},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

Checkout Events

checkout.completed

Emitted when checkout session completes successfully.

{
id: "evt_co001",
type: "checkout.completed",
data: {
id: "cs_abc123",
customerId: "cus_abc123",
planId: "plan_pro",
priceId: "price_pro_monthly",
mode: "subscription",
status: "completed",
successUrl: "https://example.com/success",
cancelUrl: "https://example.com/cancel",
metadata: {},
completedAt: Date,
expiresAt: Date,
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

checkout.expired

Emitted when checkout session expires.

{
id: "evt_co002",
type: "checkout.expired",
data: {
id: "cs_abc124",
customerId: null,
planId: "plan_pro",
priceId: "price_pro_monthly",
mode: "subscription",
status: "expired",
successUrl: "https://example.com/success",
cancelUrl: "https://example.com/cancel",
metadata: {},
completedAt: null,
expiresAt: Date, // Expired timestamp
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

Vendor Events

vendor.created

Emitted when a vendor is created.

{
id: "evt_ven001",
type: "vendor.created",
data: {
id: "ven_abc123",
name: "Acme Inc",
email: "vendor@acme.com",
revenueSharePercentage: 80, // 80% to vendor
status: "active",
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

vendor.updated

Emitted when vendor information is updated.

{
id: "evt_ven002",
type: "vendor.updated",
data: {
id: "ven_abc123",
name: "Acme Inc",
email: "vendor@acme.com",
revenueSharePercentage: 85, // Updated from 80%
status: "active",
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

vendor.payout

Emitted when a payout is made to a vendor.

{
id: "evt_ven003",
type: "vendor.payout",
data: {
id: "po_abc123",
vendorId: "ven_abc123",
amount: 8000, // $80.00
currency: "USD",
status: "completed",
periodStart: Date,
periodEnd: Date,
metadata: {
transactionId: "txn_123",
method: "bank_transfer"
},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

Add-on Events

addon.created

Emitted when a new add-on is created.

{
id: "evt_addon001",
type: "addon.created",
data: {
id: "addon_abc123",
name: "Extra Storage",
description: "100GB additional storage",
active: true,
unitAmount: 1000,
currency: "USD",
billingInterval: "month",
billingIntervalCount: 1,
compatiblePlanIds: [],
allowMultiple: true,
maxQuantity: null,
entitlements: ["extra_storage"],
limits: [
{
key: "storage_gb",
value: 100,
action: "increment"
}
],
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

addon.updated

Emitted when an add-on is updated.

{
id: "evt_addon002",
type: "addon.updated",
data: {
id: "addon_abc123",
name: "Extra Storage",
description: "100GB additional storage",
active: true,
unitAmount: 1200, // Price updated
currency: "USD",
billingInterval: "month",
billingIntervalCount: 1,
compatiblePlanIds: [],
allowMultiple: true,
maxQuantity: null,
entitlements: ["extra_storage"],
limits: [...],
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

addon.deleted

Emitted when an add-on is deleted.

{
id: "evt_addon003",
type: "addon.deleted",
data: {
id: "addon_abc123",
name: "Extra Storage",
description: "100GB additional storage",
active: false, // Set to inactive
unitAmount: 1000,
currency: "USD",
billingInterval: "month",
billingIntervalCount: 1,
compatiblePlanIds: [],
allowMultiple: true,
maxQuantity: null,
entitlements: ["extra_storage"],
limits: [...],
metadata: {},
createdAt: Date,
updatedAt: Date
},
livemode: true,
createdAt: Date
}

Subscription Add-on Events

subscription.addon_added

Emitted when an add-on is added to a subscription.

{
id: "evt_subaddon001",
type: "subscription.addon_added",
data: {
subscription: {
id: "sub_abc123",
customerId: "cus_abc123",
planId: "plan_pro",
// ... full subscription object
},
subscriptionAddOn: {
id: "sa_abc123",
subscriptionId: "sub_abc123",
addOnId: "addon_abc123",
quantity: 2,
unitAmount: 1000,
currency: "USD",
status: "active",
addedAt: Date,
canceledAt: null,
expiresAt: null,
metadata: {},
createdAt: Date,
updatedAt: Date
},
addon: {
id: "addon_abc123",
name: "Extra Storage",
// ... full add-on object
}
},
livemode: true,
createdAt: Date
}

subscription.addon_removed

Emitted when an add-on is removed from a subscription.

{
id: "evt_subaddon002",
type: "subscription.addon_removed",
data: {
subscription: { /* full subscription */ },
subscriptionAddOn: {
id: "sa_abc123",
subscriptionId: "sub_abc123",
addOnId: "addon_abc123",
quantity: 2,
unitAmount: 1000,
currency: "USD",
status: "canceled",
addedAt: Date,
canceledAt: Date, // Set when removed
expiresAt: null,
metadata: {},
createdAt: Date,
updatedAt: Date
},
addon: { /* full add-on */ }
},
livemode: true,
createdAt: Date
}

subscription.addon_updated

Emitted when a subscription add-on is updated (e.g., quantity changed).

{
id: "evt_subaddon003",
type: "subscription.addon_updated",
data: {
subscription: { /* full subscription */ },
subscriptionAddOn: {
id: "sa_abc123",
subscriptionId: "sub_abc123",
addOnId: "addon_abc123",
quantity: 5, // Updated from 2
unitAmount: 1000,
currency: "USD",
status: "active",
addedAt: Date,
canceledAt: null,
expiresAt: null,
metadata: {},
createdAt: Date,
updatedAt: Date
},
addon: { /* full add-on */ }
},
livemode: true,
createdAt: Date
}

Event Handling Example

import type { QZPayEvent, QZPayEventMap } from '@qazuor/qzpay-core';
// Type-safe event handler
billing.on('subscription.created', async (event: QZPayEvent<QZPayEventMap['subscription.created']>) => {
console.log('New subscription:', event.data.id);
console.log('Customer:', event.data.customerId);
console.log('Plan:', event.data.planId);
console.log('Status:', event.data.status);
});
// Handle payment failures
billing.on('payment.failed', async (event) => {
await sendEmail(event.data.customerId, 'payment_failed', {
amount: event.data.amount / 100,
reason: event.data.failureReason,
retryUrl: `${APP_URL}/billing/retry`
});
});
// Handle add-on additions
billing.on('subscription.addon_added', async (event) => {
console.log('Add-on added to subscription');
console.log('Subscription:', event.data.subscription.id);
console.log('Add-on:', event.data.addon.name);
console.log('Quantity:', event.data.subscriptionAddOn.quantity);
});