Processing Order and Subscription Webhook Events
Once a webhook payload is verified as authentic, parse the event action type to register orders, link software licenses, or block past-due subscriptions.
1. Key Webhook Event Action Names
Monitor these events to synchronize status values inside your database:
order_created: Fired when a customer completes a one-time purchase or initial subscription purchase. Includes billing details and order totals.subscription_created: Fired when a subscription is registered. Contains the status (activeortrialing).subscription_payment_failed: Fired when recurring invoice payments fail. Use this to notify the customer and block premium access.
2. Implementing the Event Handler Engine
Extend your route handler function to update Postgres/MySQL states:
// app/api/webhooks/lemonsqueezy/route.ts (Continued)
import { prisma } from "../../../lib/prisma";
export async function processLemonEvent(payload: any) {
const eventName = payload.meta.event_name;
const data = payload.data;
const attributes = data.attributes;
switch (eventName) {
case "order_created": {
const customerId = attributes.customer_id;
const orderNumber = attributes.order_number;
const totalAmount = attributes.total_formatted;
// Extract custom user ID from transaction pass-through data
const userId = payload.meta.custom_data?.userId;
if (userId) {
await prisma.order.create({
data: {
userId: userId,
lemonCustomerId: String(customerId),
orderNumber: String(orderNumber),
amount: totalAmount,
status: "paid",
},
});
}
break;
}
case "subscription_created": {
const subscriptionId = data.id;
const customerId = attributes.customer_id;
const status = attributes.status; // e.g. "active"
const userId = payload.meta.custom_data?.userId;
if (userId) {
await prisma.user.update({
where: { id: userId },
data: {
lemonCustomerId: String(customerId),
lemonSubscriptionId: String(subscriptionId),
subscriptionActive: status === "active" || status === "on_trial",
},
});
}
break;
}
case "subscription_payment_failed": {
const subscriptionId = data.id;
// Restrict access until payment details are updated
await prisma.user.updateMany({
where: { lemonSubscriptionId: String(subscriptionId) },
data: {
subscriptionActive: false,
},
});
break;
}
default:
console.log(`Ignored event action: ${eventName}`);
}
}3. Important Design Advice
- Atomic Transactions: If an order creates multiple side-effects (such as generating a license key and adding a customer profile row), use a Prisma
$transactiondatabase helper block to make sure either all writes succeed or all roll back. - Return 200 OK Fast: Webhook servers expect your server to return an HTTP status code 200 immediately. Do not execute slow, complex tasks (like processing video files or sending long emails) synchronously inside the webhook call; trigger background queues instead.
Published on Last updated: