Voltar ao blog

Como testar webhooks do Stripe: guia prático com exemplos

Integrar com o Stripe é quase obrigatório para quem trabalha com pagamentos online. E a parte mais crítica da integração? Os webhooks. Eles são o mecanismo que avisa seu sistema quando um pagamento foi confirmado, uma assinatura cancelada ou uma disputa aberta.

Neste guia, vamos mostrar como testar webhooks do Stripe de forma eficiente, do ambiente de desenvolvimento até a produção.

Por que os webhooks do Stripe são importantes?

O Stripe usa webhooks para notificar sua aplicação sobre eventos assíncronos. Sem eles, você não sabe se:

  • Um pagamento via Pix ou boleto foi confirmado
  • Uma assinatura foi renovada ou cancelada
  • Um cartão foi recusado após a tentativa
  • Uma disputa (chargeback) foi aberta

Regra de ouro: nunca confie apenas no retorno do checkout. O webhook é a fonte de verdade para o status do pagamento.

Anatomia de um webhook do Stripe

Quando um evento acontece, o Stripe envia um POST para sua URL com esta estrutura:

{
  "id": "evt_1ABC123",
  "object": "event",
  "type": "payment_intent.succeeded",
  "created": 1713456000,
  "data": {
    "object": {
      "id": "pi_3XYZ789",
      "amount": 4900,
      "currency": "brl",
      "status": "succeeded",
      "payment_method": "pm_card_visa",
      "metadata": {
        "order_id": "12345"
      }
    }
  }
}

Headers importantes

HeaderValor
Content-Typeapplication/json
Stripe-SignatureAssinatura HMAC para validação
User-AgentStripe/1.0 (...)

Passo 1: Configure o endpoint no Stripe

No Dashboard do Stripe, adicione um endpoint:

  1. Acesse Developers → Webhooks
  2. Clique em Add endpoint
  3. Cole a URL do seu endpoint
  4. Selecione os eventos que quer receber

Quais eventos escutar?

Para pagamentos, os mais comuns são:

  • payment_intent.succeeded — pagamento confirmado
  • payment_intent.payment_failed — pagamento falhou
  • charge.refunded — reembolso processado
  • customer.subscription.updated — assinatura alterada
  • customer.subscription.deleted — assinatura cancelada
  • invoice.payment_succeeded — fatura paga
  • checkout.session.completed — checkout finalizado

Passo 2: O problema do localhost

Em desenvolvimento, seu servidor roda em localhost:3000 ou similar. O Stripe não consegue acessar essa URL. Você tem três opções:

Opção A: Stripe CLI (oficial)

stripe listen --forward-to localhost:3000/webhook

Funciona bem, mas:

  • Precisa instalar a CLI
  • A URL muda a cada sessão
  • Sem histórico visual dos requests

Opção B: ngrok

ngrok http 3000

Cria um tunnel público, mas:

  • URL temporária (muda a cada reinício no plano free)
  • Precisa atualizar o endpoint no Stripe toda vez
  • Sem replay de webhooks

Opção C: HookScope (recomendado)

  1. Crie um endpoint no HookScope
  2. Copie a URL permanente gerada
  3. Cole no Stripe como webhook endpoint
  4. Receba webhooks no localhost via dashboard (forward automático) ou CLI:
# Instale o CLI
dotnet tool install -g HookScope.Cli

# Autentique e escute
hookscope login --api-key SUA_CHAVE
hookscope listen meu-endpoint --to http://localhost:3000/webhook

Vantagens:

  • URL permanente — configure uma vez, nunca mais mude
  • CLI para debug local — receba webhooks no localhost sem tunnels via hookscope listen
  • Replay com 1 clique — re-envie qualquer webhook sem re-disparar o evento
  • Visualização real-time — veja headers, body e status instantaneamente
  • Forward automático — encaminha para seu localhost em background
  • Histórico completo — acesse webhooks de dias atrás

Passo 3: Valide a assinatura do webhook

Nunca processe um webhook sem validar a assinatura. Qualquer pessoa poderia enviar requests falsos para sua URL.

Node.js / Express

const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["stripe-signature"];
  const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

  let event;
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    console.error("Assinatura inválida:", err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Processa o evento
  switch (event.type) {
    case "payment_intent.succeeded":
      const paymentIntent = event.data.object;
      console.log("Pagamento confirmado:", paymentIntent.id);
      // Atualiza pedido no banco
      break;
    case "payment_intent.payment_failed":
      console.log("Pagamento falhou");
      break;
  }

  res.json({ received: true });
});

C# / ASP.NET Core

[HttpPost("webhook")]
public async Task<IActionResult> HandleWebhook()
{
    var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
    var signature = Request.Headers["Stripe-Signature"];

    try
    {
        var stripeEvent = EventUtility.ConstructEvent(
            json, signature, _webhookSecret
        );

        switch (stripeEvent.Type)
        {
            case EventTypes.PaymentIntentSucceeded:
                var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
                _logger.LogInformation("Pagamento {Id} confirmado", paymentIntent?.Id);
                break;
        }

        return Ok(new { received = true });
    }
    catch (StripeException e)
    {
        _logger.LogError("Assinatura inválida: {Message}", e.Message);
        return BadRequest();
    }
}

Dica: ao usar o HookScope com forward (via dashboard ou CLI), o webhook chega no seu localhost com todos os headers originais, incluindo o Stripe-Signature. A validação funciona normalmente.

Passo 4: Teste com o Stripe CLI

O Stripe CLI permite disparar eventos de teste:

# Dispara um evento específico
stripe trigger payment_intent.succeeded

# Dispara com dados customizados
stripe trigger checkout.session.completed \
  --override checkout_session:metadata.order_id=12345

Esses eventos são enviados para o endpoint configurado — incluindo seu endpoint no HookScope.

Passo 5: Debug quando algo dá errado

Os problemas mais comuns com webhooks do Stripe:

O webhook não chega

  • Verifique se a URL está correta e acessível
  • Confirme que os eventos certos estão selecionados no Dashboard
  • Verifique o log de tentativas no Stripe: Developers → Webhooks → seu endpoint → Eventos recentes

Erro 400/500 na resposta

  • Verifique se está usando express.raw() (Node.js) ou lendo o body como string (não JSON parseado)
  • A validação de assinatura precisa do body raw, não parseado

Assinatura inválida

  • Verifique se o STRIPE_WEBHOOK_SECRET está correto (começa com whsec_)
  • Em desenvolvimento, use o secret do Stripe CLI, não do Dashboard
  • Confirme que o body não está sendo modificado por middleware

Replay para re-testar

Com o HookScope, quando você identifica o bug e corrige o código, basta clicar em Replay no webhook que falhou. Ele é re-enviado para seu servidor com o payload original — sem precisar re-disparar o evento no Stripe.

Checklist para produção

Antes de ir para produção com webhooks do Stripe:

  • Endpoint configurado com URL HTTPS de produção
  • Assinatura (Stripe-Signature) validada em toda request
  • Processamento idempotente (verificar event.id antes de processar)
  • Retorno 200 rápido (processar em background se necessário)
  • Monitoramento de falhas (alertas se webhook retorna erro)
  • Retry handling — Stripe faz até 3 tentativas em caso de falha
  • Logging completo de eventos recebidos e processados

Conclusão

Testar webhooks do Stripe não precisa ser doloroso. Com as ferramentas certas, você consegue:

  1. Capturar webhooks com URL permanente
  2. Inspecionar cada detalhe do payload
  3. Re-enviar webhooks para testar correções
  4. Encaminhar automaticamente para seu ambiente local (via dashboard ou CLI)

Crie sua conta gratuita no HookScope e simplifique seus testes de integração com o Stripe.