Para probar los webhooks de Lemon Squeezy en localhost, inicia tu aplicación, expón su ruta de webhook con un túnel HTTPS público, crea un webhook en modo de prueba en Configuración → Webhooks, y verifica el cuerpo de la solicitud sin procesar contra el X-Signature encabezado. Utiliza una prueba de pago para datos de pedidos reales y los controles de simulación de suscripción de Lemon Squeezy para eventos del ciclo de vida que son difíciles de esperar.
Cómo se ve una configuración local correcta
Lemon Squeezy envía un HTTP POST desde su infraestructura, por lo que no puede alcanzar localhost en tu computadora. El túnel termina el TLS público y envía el método, el cuerpo y los encabezados sin cambios a una ruta local como http://localhost:3000/api/webhooks/lemonsqueezy.
Una prueba completa tiene cuatro partes: modo de prueba Lemon Squeezy, una URL de callback pública, el mismo secreto de firma en el panel de control y en el entorno local, y un manejador que verifica antes de cambiar el estado de la suscripción o la licencia. Esto es más útil que publicar JSON copiado con curl, porque una entrega real prueba tanto el enrutamiento como la firma generada por el proveedor.
Crea el webhook y envía un evento de prueba
- Ejecute su aplicación en el puerto 3000 y verifique que la ruta acepte
POST. - Comienza el túnel con
npx portpreview 3000. - Cambia el panel de Lemon Squeezy al modo de prueba.
- Abre Configuración → Webhooks y agrega
https://your-subdomain.portpreview.dev/api/webhooks/lemonsqueezy. - Genera un secreto de firma aleatorio fuerte, guárdalo como
LEMONSQUEEZY_WEBHOOK_SECRET, y seleccione solo los eventos que su aplicación consume. - Crea una compra de prueba o utiliza la acción de simulación de suscripción en modo de prueba disponible, luego inspecciona el registro de entregas y tu respuesta local.
El oficial Documentación del webhook de Lemon Squeezy te permite inspeccionar cargas útiles recientes y reenviar webhooks grabados desde la página de configuración. Un reenvío es valioso para las pruebas de regresión, pero tu aplicación debe tratarlo como un duplicado en lugar de un segundo evento de negocio.
Verificar X-Signature antes de analizar JSON
Lemon Squeezy calcula un resumen HMAC-SHA256 sobre la carga útil de la solicitud usando el secreto de firma y envía el resumen en hexadecimal en X-Signature. Calcula el resumen sobre los bytes exactos del cuerpo sin procesar y compara búferes de igual longitud con una función de tiempo constante. El principal documentación de solicitudes de firma incluye ejemplos para Node, PHP, Python y Ruby.
// app/api/webhooks/lemonsqueezy/route.ts
import crypto from 'node:crypto';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const secret = process.env.LEMONSQUEEZY_WEBHOOK_SECRET;
if (!secret) return new NextResponse('server misconfigured', { status: 500 });
const rawBody = await request.text();
const suppliedHex = request.headers.get('x-signature') ?? '';
if (!/^[0-9a-f]{64}$/i.test(suppliedHex)) {
return new NextResponse('invalid signature', { status: 401 });
}
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest();
const supplied = Buffer.from(suppliedHex, 'hex');
if (supplied.length !== expected.length ||
!crypto.timingSafeEqual(supplied, expected)) {
return new NextResponse('invalid signature', { status: 401 });
}
const event = JSON.parse(rawBody);
await acceptEvent(event);
return NextResponse.json({ received: true });
}
No llames request.json() primero. Re-serializar el objeto resultante puede cambiar bytes que parecen insignificantes y romper el HMAC. La verificación de longitud también es esencial porque Node's timingSafeEqual lanza cuando las longitudes del búfer difieren. Oficial de Lemon Squeezy Tutorial de webhook de Next.js sigue la misma secuencia de cuerpo en bruto.
Comprenda la carga útil antes de actualizar su base de datos
El cuerpo es un objeto de recurso JSON:API. meta.event_name identifica el evento, meta.custom_data transporta datos pasados a través del proceso de pago, y data contiene el recurso de pedido, suscripción o clave de licencia. Lemon Squeezy también envía X-Event-Name; trate el cuerpo firmado como la entrada autorizada y use el encabezado para diagnósticos de enrutamiento.
Pedidos e identidad del cliente
Para order_created, asigna la compra a un usuario interno usando un identificador generado por el servidor en los datos personalizados de pago. No confíes en un ID de usuario proporcionado por el cliente sin verificar la propiedad. Guarda el ID de recurso y el ID de variante de Lemon Squeezy para que los futuros eventos de suscripción o reembolso puedan reconciliarse.
Las suscripciones son una máquina de estados
Mango subscription_created, subscription_updated, subscription_cancelled, subscription_resumed, y subscription_expired como transiciones distintas. La cancelación puede significar que el acceso sigue siendo válido hasta el final del período de facturación; la expiración es la señal más fuerte de que la suscripción ha terminado. Modele fechas y estado en lugar de alternar un valor booleano.
Fallos de pago y recuperación
Pruebas de flujos de renovación fallidos y recuperados donde el modo de prueba los soporta. Un pago fallido no debería necesariamente revocar el acceso de inmediato; aplique el período de gracia definido por la política de su producto y permita que pagos posteriores o eventos de suscripción ajusten la cuenta al estado más reciente del proveedor.
Los reintentos requieren un procesamiento idempotente
Según Lemon Squeezy referencia de solicitud de webhook, a 200 Las marcas de respuesta capturan como exitoso. Otros estados se reintentan hasta tres veces más con retrasos exponenciales mostrados aproximadamente como 5, 25 y 125 segundos. El reenvío del panel crea otra razón por la que el mismo evento lógico puede llegar más de una vez.
Construya una clave de idempotencia a partir de campos de carga útil firmados estables. Un enfoque práctico es una fila única en la base de datos para el ID del recurso del webhook, el nombre del evento y la marca de tiempo del evento, o un hash del cuerpo firmado sin procesar cuando no hay un ID de evento dedicado presente. Inserte ese recibo y actualice el estado de la aplicación en una sola transacción. Devuelva 200 para un duplicado completado previamente.
BEGIN;
INSERT INTO webhook_receipts (fingerprint, received_at)
VALUES ($1, now())
ON CONFLICT DO NOTHING;
-- Continue only if one row was inserted.
UPDATE subscriptions SET provider_status = $2 WHERE provider_id = $3;
COMMIT;
No haga que la entrega de correos electrónicos o una llamada lenta a una API de terceros forme parte del camino de reconocimiento. Almacene un trabajo en la misma transacción, responda y deje que un trabajador maneje los efectos secundarios. El más amplio guía de reintento de webhook e idempotencia cubre este patrón.
Solución de problemas de las pruebas locales de webhooks de Lemon Squeezy
Ninguna solicitud llega a la aplicación
Comprueba que el túnel sigue funcionando, que la devolución de llamada contiene el sufijo de la ruta y que tu framework tiene un manejador POST en esa ruta exacta. Llama al endpoint público tú mismo. Un túnel 502 apunta al puerto local; un framework 404 apunta a la colocación de la ruta.
Cada firma es inválida
Confirma que el modo de prueba y el modo en vivo no estén utilizando diferentes registros de webhook o secretos. Lee el cuerpo una vez como texto o bytes, no recortes ni el cuerpo ni el secreto, y asegúrate de que el middleware global de JSON no se ejecute primero. Inspecciona si X-Signature llegó, pero nunca imprimas el secreto de firma.
La entrega sigue reintentando
Lemon Squeezy espera 200, no una redirección a una página de inicio de sesión. Excluya la ruta del webhook del middleware de CSRF y de la sesión de usuario mientras se mantiene la verificación de la firma. Capture los errores de la base de datos, mantenga el trabajo corto y asegúrese de que se devuelva una respuesta de éxito en cada rama de evento manejada.
La simulación de suscripción no está disponible
Los controles de simulación requieren datos de suscripción en modo de prueba adecuados. Algunas simulaciones de pago necesitan historial de renovaciones. Cree primero la suscripción de prueba o compra de prueba requerida, luego use el menú de acciones de suscripción descrito en Lemon Squeezy. guía de desarrollador de webhook.
Lista de verificación de seguridad y puesta en marcha
- Genera un secreto de firma aleatorio, guárdalo en la configuración del entorno y rótalo si se expone.
- Verifique el HMAC sobre el cuerpo bruto antes de analizar JSON, escribir en la base de datos, generar licencias o realizar cambios de acceso.
- Use una comparación segura en el tiempo y rechace firmas faltantes o mal formadas.
- Mantenga los secretos y registros del modo de prueba y del webhook en vivo separados.
- Permitir solo POST, aplicar un límite de tamaño de cuerpo razonable, limitar la frecuencia de solicitudes inválidas y ocultar los datos de los clientes en los registros.
- Prueba duplicados, eventos del ciclo de vida fuera de orden, tiempo de inactividad de la base de datos y recuperación de tiempo de espera antes de reemplazar el túnel con tu URL de producción.
Un proceso de pago exitoso y sencillo solo demuestra que llegó un evento. Una integración lista para producción demuestra el rechazo de firmas, la seguridad frente a duplicados, la convergencia del ciclo de vida y la recuperación de una respuesta que no sea 200. Para los problemas criptográficos subyacentes, vea el guía de verificación de firmas.
