Last active
August 6, 2024 20:08
-
-
Save amosbastian/e403e1d8ccf4f7153f7840dd11a85a69 to your computer and use it in GitHub Desktop.
Lemon Squeezy webhook using the new route handler in Next.js 13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import crypto from "crypto"; | |
import { listAllSubscriptions } from "lemonsqueezy.ts"; | |
import { NextRequest } from "next/server"; | |
// Put this in your billing lib and just import the type instead | |
type LemonsqueezySubscription = Awaited<ReturnType<typeof listAllSubscriptions>>["data"][number]; | |
// Add more events here if you want | |
// https://docs.lemonsqueezy.com/api/webhooks#event-types | |
type EventName = | |
| "order_created" | |
| "order_refunded" | |
| "subscription_created" | |
| "subscription_cancelled" | |
| "subscription_resumed" | |
| "subscription_expired" | |
| "subscription_paused" | |
| "subscription_unpaused" | |
| "subscription_payment_failed" | |
| "subscription_payment_success" | |
| "subscription_payment_recovered"; | |
type Payload = { | |
meta: { | |
test_mode: boolean; | |
event_name: EventName; | |
}; | |
data: LemonsqueezySubscription; | |
}; | |
export const POST = async (req: NextRequest) => { | |
try { | |
const rawBody = await req.text(); | |
const hmac = crypto.createHmac("sha256", process.env.LEMON_SQUEEZY_WEBHOOK_SECRET || ""); | |
const digest = Buffer.from(hmac.update(rawBody).digest("hex"), "utf8"); | |
const signature = Buffer.from(req.headers.get("x-signature") as string, "utf8"); | |
if (!crypto.timingSafeEqual(digest, signature)) { | |
return new Response("Invalid signature.", { | |
status: 400, | |
}); | |
} | |
const payload = JSON.parse(rawBody); | |
const { | |
meta: { event_name: eventName }, | |
data: subscription, | |
} = payload as Payload; | |
switch (eventName) { | |
case "order_created": | |
// Do stuff here if you are using orders | |
break; | |
case "order_refunded": | |
// Do stuff here if you are using orders | |
break; | |
case "subscription_created": | |
case "subscription_cancelled": | |
case "subscription_resumed": | |
case "subscription_expired": | |
case "subscription_paused": | |
case "subscription_unpaused": | |
case "subscription_payment_failed": | |
case "subscription_payment_success": | |
case "subscription_payment_recovered": | |
// Do something with the subscription here, like syncing to your database | |
console.log(subscription); | |
break; | |
default: | |
throw new Error(`🤷♀️ Unhandled event: ${eventName}`); | |
} | |
} catch (error: unknown) { | |
if (typeof error === "string") { | |
return new Response("Webhook error", { | |
status: 400, | |
}); | |
} | |
if (error instanceof Error) { | |
return new Response(`Webhook error: ${error.message}`, { | |
status: 400, | |
}); | |
} | |
throw error; | |
} | |
return new Response(null, { | |
status: 200, | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks a lot, long live open source!