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
| Header | Valor |
|---|---|
Content-Type | application/json |
Stripe-Signature | Assinatura HMAC para validação |
User-Agent | Stripe/1.0 (...) |
Passo 1: Configure o endpoint no Stripe
No Dashboard do Stripe, adicione um endpoint:
- Acesse Developers → Webhooks
- Clique em Add endpoint
- Cole a URL do seu endpoint
- Selecione os eventos que quer receber
Quais eventos escutar?
Para pagamentos, os mais comuns são:
payment_intent.succeeded— pagamento confirmadopayment_intent.payment_failed— pagamento falhoucharge.refunded— reembolso processadocustomer.subscription.updated— assinatura alteradacustomer.subscription.deleted— assinatura canceladainvoice.payment_succeeded— fatura pagacheckout.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)
- Crie um endpoint no HookScope
- Copie a URL permanente gerada
- Cole no Stripe como webhook endpoint
- 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_SECRETestá correto (começa comwhsec_) - 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.idantes de processar) - Retorno
200rá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:
- Capturar webhooks com URL permanente
- Inspecionar cada detalhe do payload
- Re-enviar webhooks para testar correções
- 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.