Lemon Squeezy integration

Forward Lemon Squeezy order events to Partli for automatic sale tracking.

Lemon Squeezy works the same way as Stripe — capture the click ID at checkout time, then forward order events to Partli'strack/sale endpoint.

1. Pass the click ID through checkout

Append ?checkout[custom][partli_click_id]=... to the Lemon Squeezy checkout URL when you generate it for the user:

const clickId = cookies.get("pref_cid") ?? "";
const checkoutUrl = `https://yourstore.lemonsqueezy.com/buy/${variantId}?checkout[custom][partli_click_id]=${clickId}`;

2. Handle order_created webhook

// app/api/webhooks/lemonsqueezy/route.ts
import crypto from "crypto";

export async function POST(req: Request) {
  const raw = await req.text();
  const signature = req.headers.get("x-signature")!;
  const expected = crypto
    .createHmac("sha256", process.env.LS_WEBHOOK_SECRET!)
    .update(raw)
    .digest("hex");
  if (signature !== expected) return new Response(null, { status: 400 });

  const event = JSON.parse(raw);
  if (event.meta.event_name !== "order_created") {
    return new Response(null, { status: 200 });
  }

  const order = event.data.attributes;
  const clickId = event.meta.custom_data?.partli_click_id;
  if (!clickId) return new Response(null, { status: 200 });

  await fetch("https://partli.app/api/track/sale", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PARTLI_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      externalId: order.identifier,
      customerExternalId: order.customer_id,
      amountCents: order.total,
      currency: order.currency,
      clickId,
    }),
  });

  return new Response(null, { status: 200 });
}

Refunds

Refund clawbacks are not yet automated through a public endpoint. If a Lemon Squeezy order is refunded, find the corresponding commission in your dashboard and mark it as clawed back manually. As long as the commission is still in pending or approved (not yet paid out), no money has moved.