Todos os artigos
Eventos de webhook de assinatura e licença passando por um túnel HTTPS para um manipulador de desenvolvimento local verificado.
Lemon Squeezywebhookssubscriptionslocal testing

Testar webhooks do Lemon Squeezy no localhost

Para testar webhooks do Lemon Squeezy no localhost, inicie seu aplicativo, exponha sua rota de webhook com um túnel HTTPS público, crie um webhook em modo de teste em Configurações → Webhooks e verifique o corpo da solicitação bruta em relação ao X-Signature cabeçalho. Use um teste de checkout para dados de pedidos reais e os controles de simulação de assinatura do Lemon Squeezy para eventos do ciclo de vida que são difíceis de esperar.

Como é uma configuração local correta

Lemon Squeezy envia um HTTP POST de sua infraestrutura, então não pode alcançar localhost no seu computador. O túnel termina o TLS público e encaminha o método, corpo e cabeçalhos inalterados para uma rota local, como http://localhost:3000/api/webhooks/lemonsqueezy.

Um teste completo tem quatro partes: modo de teste Lemon Squeezy, uma URL de callback pública, o mesmo segredo de assinatura no painel e no ambiente local, e um manipulador que verifica antes de alterar o estado da assinatura ou da licença. Isso é mais útil do que postar JSON copiado com curl, porque uma entrega real testa tanto o roteamento quanto a assinatura gerada pelo provedor.

Crie o webhook e envie um evento de teste

  1. Execute seu aplicativo na porta 3000 e verifique se a rota é aceita POST.
  2. Inicie o túnel com npx portpreview 3000.
  3. Mude o painel Lemon Squeezy para o modo de teste.
  4. Abra Configurações → Webhooks e adicione https://your-subdomain.portpreview.dev/api/webhooks/lemonsqueezy.
  5. Gere um segredo de assinatura aleatório forte, salve-o como LEMONSQUEEZY_WEBHOOK_SECRET, e selecione apenas os eventos que seu aplicativo consome.
  6. Crie uma compra de teste ou use a ação de simulação de assinatura em modo de teste disponível, depois inspecione o log de entrega e sua resposta local.

O oficial Documentação de webhook do Lemon Squeezy permite inspecionar cargas úteis recentes e reenviar webhooks gravados a partir da página de configurações. Um reenvio é valioso para testes de regressão, mas sua aplicação deve tratá-lo como um duplicado em vez de um segundo evento de negócio.

Verificar X-Signature antes de analisar JSON

O Lemon Squeezy calcula um digest HMAC-SHA256 sobre o payload da requisição usando o segredo de assinatura e envia o digest hexadecimal em X-SignatureCalcule o resumo sobre os bytes brutos exatos do corpo e compare buffers de comprimento igual com uma função de tempo constante. O principal documentação de solicitações de assinatura inclui exemplos para Node, PHP, Python e 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 });
}

Não ligar request.json() primeiro. Re-serializar o objeto resultante pode alterar bytes que parecem insignificantes e quebrar o HMAC. A verificação de comprimento também é essencial porque o Node's timingSafeEqual lança quando os comprimentos do buffer diferem. Oficial do Lemon Squeezy Tutorial de webhook do Next.js segue a mesma sequência de corpo cru.

Entenda o payload antes de atualizar seu banco de dados

O corpo é um objeto de recurso JSON:API. meta.event_name identifica o evento, meta.custom_data transporta dados passados pelo checkout, e data contém o recurso de pedido, assinatura ou chave de licença. O Lemon Squeezy também envia X-Event-Name; trate o corpo assinado como a entrada autoritativa e use o cabeçalho para diagnósticos de roteamento.

Pedidos e identidade do cliente

Para order_created, mapeie a compra para um usuário interno usando um identificador gerado pelo servidor nos dados personalizados do checkout. Não confie em um ID de usuário fornecido pelo cliente sem verificar a propriedade. Persista o ID de recurso e o ID de variante do Lemon Squeezy para que eventos posteriores de assinatura ou reembolso possam ser reconciliados.

Assinaturas são uma máquina de estados

Alça subscription_created, subscription_updated, subscription_cancelled, subscription_resumed, e subscription_expired como transições distintas. Cancelamento pode significar que o acesso permanece válido até o final do período de cobrança; expiração é o sinal mais forte de que a assinatura terminou. Modele datas e status em vez de alternar um booleano.

Falhas de pagamento e recuperação

Testou fluxos de renovação com falha e recuperação onde o modo de teste os suporta. Um pagamento falho não deve necessariamente revogar o acesso imediatamente; aplique o período de carência definido pela política do seu produto e deixe que pagamentos ou eventos de assinatura posteriores façam a conta convergir para o estado mais recente do provedor.

Retentativas requerem processamento idempotente

De acordo com o Lemon Squeezy referência de solicitação de webhook, a 200 As marcas de resposta capturam como bem-sucedido. Outros status são tentados novamente até mais três vezes com atrasos exponenciais mostrados aproximadamente como 5, 25 e 125 segundos. Reenvios no painel criam outra razão pela qual o mesmo evento lógico pode chegar mais de uma vez.

Crie uma chave de idempotência a partir de campos estáveis do payload assinado. Uma abordagem prática é uma linha de banco de dados única para o ID do recurso do webhook, nome do evento e timestamp do evento, ou um hash do corpo assinado bruto quando nenhum ID de evento dedicado estiver presente. Insira esse recibo e atualize o estado da aplicação em uma única transação. Retorne 200 para um duplicado previamente concluído.

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;

Não inclua a entrega de e-mail ou uma chamada lenta de API de terceiros no caminho de reconhecimento. Armazene um trabalho na mesma transação, responda e deixe um trabalhador lidar com os efeitos colaterais. O mais amplo guia de retentativa de webhook e idempotência cobre este padrão.

Solução de problemas de testes de webhook do Lemon Squeezy no localhost

Nenhuma solicitação chega ao aplicativo

Verifique se o túnel ainda está funcionando, se o callback contém o sufixo da rota e se seu framework possui um manipulador POST nesse caminho exato. Chame o endpoint público você mesmo. Um túnel 502 aponta para a porta local; um framework 404 aponta para a colocação da rota.

Cada assinatura é inválida

Confirme se o modo de teste e o modo ao vivo não estão usando registros de webhook ou segredos diferentes. Leia o corpo uma vez como texto ou bytes, não apare nem o corpo nem o segredo, e garanta que o middleware global de JSON não seja executado primeiro. Verifique se X-Signature chegou, mas nunca imprima o segredo de assinatura.

A entrega continua tentando novamente

Lemon Squeezy espera 200, não é um redirecionamento para uma página de login. Exclua o caminho do webhook do middleware CSRF e de sessão do usuário, mantendo a verificação de assinatura. Capture erros de banco de dados, mantenha o trabalho curto e garanta que uma resposta de sucesso seja realmente retornada em cada ramo de evento tratado.

Simulação de assinatura indisponível

Os controles de simulação exigem dados de assinatura no modo de teste adequados. Algumas simulações de pagamento precisam do histórico de renovação. Crie primeiro a assinatura de teste ou a compra de teste necessária e, em seguida, use o menu de ações de assinatura descrito no Lemon Squeezy. guia do desenvolvedor de webhook.

Checklist de segurança e de entrada em operação

  • Gere um segredo de assinatura aleatório, armazene-o na configuração do ambiente e faça a rotação se ele for exposto.
  • Verifique o HMAC sobre o corpo bruto antes da análise JSON, gravações no banco de dados, geração de licença ou alterações de acesso.
  • Use uma comparação segura em termos de tempo e rejeite assinaturas ausentes ou malformadas.
  • Mantenha os segredos e registros do modo Teste e do webhook ao vivo separados.
  • Permita apenas POST, imponha um limite razoável de tamanho de corpo, limite a taxa de solicitações inválidas e oculte os dados do cliente nos logs.
  • Teste duplicatas, eventos de ciclo de vida fora de ordem, tempo de inatividade do banco de dados e recuperação de tempo limite antes de substituir o túnel pelo seu URL de produção.

Um checkout bem-sucedido no caminho feliz prova apenas que um evento chegou. Uma integração pronta para produção prova a rejeição de assinatura, segurança contra duplicatas, convergência do ciclo de vida e recuperação de uma resposta não 200. Para os riscos criptográficos subjacentes, veja o guia de verificação de assinatura.

Perguntas frequentes

Como faço para testar webhooks do Lemon Squeezy no localhost?
Exponha sua rota de webhook local com um túnel HTTPS, crie um webhook enquanto o painel estiver no modo Teste, use o mesmo segredo de assinatura localmente e acione um teste de checkout ou uma simulação de assinatura suportada.
Como é verificada a assinatura de um webhook do Lemon Squeezy?
Calcule um resumo HMAC-SHA256 sobre o corpo da solicitação bruto exato com seu segredo de assinatura, decodifique o valor hexadecimal X-Signature e compare buffers de comprimento igual com uma função segura contra variação de tempo.
O Lemon Squeezy tenta novamente webhooks que falharam?
Sim. Sua documentação diz que respostas diferentes de 200 são tentadas novamente até três vezes adicionais com retrocesso exponencial, aproximadamente após 5, 25 e 125 segundos.
Posso simular renovações de assinatura do Lemon Squeezy no modo de teste?
O modo de teste fornece ações de simulação para eventos de assinatura suportados. Algumas simulações relacionadas a pagamentos exigem uma assinatura de teste existente e dados de renovação, portanto, crie o estado de teste necessário primeiro.