Lemon Squeezy is a popular Merchant of Record for SaaS products and digital software. It sends HTTP POST webhooks for all customer actions: checkout completions, subscription renewals, payment failures, and refunds.
Lemon Squeezy Webhook Architecture
Every payload from Lemon Squeezy follows JSON:API conventions:
meta.event_name: The event name (order_created,order_refunded,subscription_created,subscription_updated,subscription_cancelled,license_key_created).meta.custom_data: Custom metadata passed during checkout.data: The primary resource object (id,type,attributes,relationships).
Verifying X-Signature
Lemon Squeezy signs every webhook request using your Signing Secret configured in Settings → Webhooks:
- Header:
X-Signature - Algorithm: HMAC-SHA256 in hexadecimal encoding over the raw UTF-8 request body.
import crypto from 'node:crypto';
export function verifyLemonSqueezy(rawBody: string, signature: string, secret: string): boolean {
const hmac = crypto.createHmac('sha256', secret);
const digest = Buffer.from(hmac.update(rawBody).digest('hex'), 'utf8');
const signatureBuffer = Buffer.from(signature, 'utf8');
return (
digest.length === signatureBuffer.length &&
crypto.timingSafeEqual(digest, signatureBuffer)
);
} Idempotency and License Keys
When order_created fires, your application typically provisions licenses or unlocks features.
Because Lemon Squeezy will retry any request that does not return HTTP 200 within 5 seconds,
ensure you store the Lemon Squeezy order ID (data.id) as a unique constraint to avoid double-issuing license keys.
Debugging Lemon Squeezy Webhooks with HookWatch
HookWatch allows inspecting the full JSON:API response hierarchy, comparing event attempt timestamps, and safely replaying failed order events after server deploys.