DEV Community

Cover image for Como Capturar e Validar Webhooks do Stripe em CI com Apidog
Lucas
Lucas

Posted on • Originally published at apidog.com

Como Capturar e Validar Webhooks do Stripe em CI com Apidog

Um cliente paga, o Stripe envia um evento payment_intent.succeeded ao seu backend, e seu endpoint precisa marcar o pedido como pago. É exatamente nesse último passo que falhas silenciosas costumam aparecer: o webhook chega, o manipulador lança um erro e ninguém percebe até surgir um ticket dizendo: “Eu paguei, mas minha conta ainda aparece como não paga”. Para evitar isso, crie um teste de CI que comprove, a cada deploy, que o evento foi recebido e que a regra de negócio foi executada.

Experimente o Apidog hoje

Webhooks são diferentes de requisições HTTP convencionais: o Stripe chama sua aplicação, e não o contrário. A maioria das ferramentas de teste de API foi projetada para enviar uma requisição e validar a resposta, o que inverte esse fluxo. Este guia mostra como validar webhooks do Stripe em CI com Apidog, usando o padrão capturar primeiro, validar depois. Para o contexto geral, consulte o guia sobre como testar webhooks e a documentação de webhooks do Stripe.

A restrição que você precisa considerar no design

A documentação do Apidog declara que o produto não suporta nativamente a escuta de webhooks. Ou seja: você não pode apontar o Stripe para uma URL do Apidog e esperar que ele capture eventos de entrada em tempo real.

A solução é persistir o evento na sua própria infraestrutura:

  1. O Stripe envia o webhook ao seu backend.
  2. Seu endpoint valida a assinatura e registra o evento no banco.
  3. O Apidog consulta esse registro.
  4. O cenário de teste valida o evento e o efeito de negócio.

Esse formato funciona bem em CI porque consultar um registro persistido é repetível, auditável e menos sujeito a condições de corrida do que tentar interceptar uma chamada externa em tempo real.

Como funciona o padrão de captura e consulta

O fluxo possui quatro componentes:

  1. Um endpoint no backend para receber webhooks do Stripe.
  2. Uma tabela, como stripe_event_logs, para armazenar os eventos recebidos.
  3. Uma conexão de banco de dados configurada no ambiente do Apidog.
  4. Um Processador Pós-Requisição que consulta os dados e executa as asserções.

Seu código é responsável por receber, verificar e persistir o webhook. O Apidog entra depois: ele lê o registro salvo e valida se o evento e seus efeitos correspondem ao esperado.

Passo 1: implemente o endpoint de captura

Crie uma rota HTTP POST que o Stripe possa chamar. Em Express, mantenha o corpo bruto da requisição para validar corretamente a assinatura.

import express from "express";
import Stripe from "stripe";

const app = express();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

app.post(
  "/webhooks/stripe",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        req.headers["stripe-signature"],
        endpointSecret
      );
    } catch (err) {
      return res.status(400).send(`Signature check failed: ${err.message}`);
    }

    // Persista o evento para que o cenário de teste possa consultá-lo.
    await db.query(
      `INSERT INTO stripe_event_logs (event_id, type, payload, handled_at)
       VALUES ($1, $2, $3, now())
       ON CONFLICT (event_id) DO NOTHING`,
      [event.id, event.type, JSON.stringify(event.data.object)]
    );

    if (event.type === "payment_intent.succeeded") {
      const intent = event.data.object;

      await markOrderPaid(intent.metadata.order_id);
    }

    return res.json({ received: true });
  }
);
Enter fullscreen mode Exit fullscreen mode

Há dois pontos essenciais nesse código:

  • Use stripe.webhooks.constructEvent() antes de confiar no payload. A validação de assinatura é obrigatória para confirmar que a requisição veio do Stripe. Veja mais detalhes sobre verificação de assinatura de webhook.
  • Persista o evento em uma tabela consultável. A cláusula ON CONFLICT (event_id) DO NOTHING mantém o processo idempotente, já que o Stripe pode reenviar o mesmo evento.

Uma estrutura mínima para a tabela poderia ser:

CREATE TABLE stripe_event_logs (
  event_id TEXT PRIMARY KEY,
  type TEXT NOT NULL,
  payload JSONB NOT NULL,
  handled_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Enter fullscreen mode Exit fullscreen mode

Se você precisar distinguir execução, recebimento e processamento concluído, considere armazenar timestamps separados, como received_at, handled_at e failed_at.

Passo 2: conecte o banco de dados ao ambiente do Apidog

Configure uma conexão de banco de dados no ambiente do Apidog usado pelo seu cenário de CI. Por exemplo:

  • Ambiente staging → banco de dados de staging.
  • Ambiente test → banco de dados isolado para testes automatizados.

A conexão precisa apontar para o mesmo banco em que o endpoint do webhook grava os eventos. Se o seu teste dispara um pagamento em staging, mas o Processador Pós-Requisição consulta outro banco, a asserção falhará mesmo que o webhook tenha sido processado corretamente.

Antes de criar o cenário, confirme:

  • O banco está acessível a partir da execução do Apidog.
  • As credenciais usadas têm permissão de leitura em stripe_event_logs.
  • O ambiente selecionado no cenário corresponde ao ambiente da aplicação.
  • A tabela não contém eventos antigos capazes de gerar falsos positivos.

Passo 3: consulte o log com um Processador Pós-Requisição

O Processador Pós-Requisição é o ponto em que o Apidog consulta o banco e valida o webhook persistido.

Um cenário para payment_intent.succeeded pode seguir esta sequência:

  1. Dispare um pagamento no modo de teste do Stripe.
  2. Aguarde o Stripe enviar o evento para /webhooks/stripe.
  3. Deixe o endpoint validar, registrar o evento e atualizar o pedido.
  4. Execute uma consulta SQL no Processador Pós-Requisição.
  5. Faça asserções sobre o evento e sobre o estado do pedido.

Uma consulta inicial pode ser:

SELECT event_id, type, payload, handled_at
FROM stripe_event_logs
WHERE type = 'payment_intent.succeeded'
ORDER BY handled_at DESC
LIMIT 1;
Enter fullscreen mode Exit fullscreen mode

Em um teste robusto, não valide apenas o type. Faça asserções sobre dados que identificam exatamente a execução atual:

  • event_id corresponde ao evento disparado.
  • type é payment_intent.succeeded.
  • payload contém o valor e os metadados esperados.
  • handled_at foi preenchido.
  • O pedido associado mudou para o estado paid.

Por exemplo, valide também o efeito de negócio:

SELECT id, status, paid_at
FROM orders
WHERE id = :order_id;
Enter fullscreen mode Exit fullscreen mode

O resultado esperado deve indicar que o pedido foi pago:

status = 'paid'
paid_at IS NOT NULL
Enter fullscreen mode Exit fullscreen mode

Isso evita um teste fraco que prova apenas que o evento foi gravado, mas não que o manipulador executou a ação necessária.

Trate o atraso de entrega

Webhooks são assíncronos. Se você consultar o banco imediatamente após disparar o pagamento, o Stripe pode ainda não ter feito a entrega.

Use uma pequena espera ou polling com retentativas antes de declarar falha. A estratégia é:

  1. Consultar o evento.
  2. Se ele não existir, aguardar alguns segundos.
  3. Repetir por um número limitado de tentativas.
  4. Falhar com uma mensagem clara se o evento não aparecer.

Evite aumentar demais o tempo de espera. O objetivo é acomodar a entrega assíncrona sem transformar seu pipeline em uma execução lenta e imprevisível.

Desenvolvimento local: encaminhe eventos em tempo real

O padrão de captura e consulta é ideal para CI. No desenvolvimento local, você precisa de uma forma de receber eventos do Stripe no seu localhost.

O Stripe CLI pode encaminhar eventos para sua aplicação local:

stripe listen --forward-to localhost:3000/webhooks/stripe
Enter fullscreen mode Exit fullscreen mode

Isso permite testar o manipulador enquanto você desenvolve. Ferramentas como Ngrok também podem expor sua porta local por meio de uma URL pública.

Use esse fluxo para desenvolvimento:

Stripe CLI ou Ngrok → localhost → endpoint de webhook
Enter fullscreen mode Exit fullscreen mode

E use este fluxo para CI:

Stripe → endpoint do ambiente → banco de dados → Apidog → asserções
Enter fullscreen mode Exit fullscreen mode

Os dois processos são complementares: encaminhamento em tempo real para desenvolver; captura e consulta para comprovar o comportamento automatizado.

Não confunda isso com o recurso nativo de Webhook do Apidog

O Apidog possui um recurso chamado Webhook, mas ele serve para definir e documentar webhooks de saída do seu sistema.

Esse recurso descreve situações em que sua aplicação chama uma URL externa quando algo acontece, por exemplo:

  • um pedido foi criado;
  • uma importação terminou;
  • uma assinatura foi cancelada.

Ele não serve para capturar chamadas de entrada do Stripe.

Para documentar um webhook de saída no Apidog:

  1. Clique no ícone + na barra lateral.
  2. Selecione New Other Protocol APIs.
  3. Escolha Webhook.
  4. Preencha Request Method, Webhook Name, Debug URL e os dados da requisição.
  5. Clique em Save.

Você pode usar a Debug URL para simular o envio durante o teste. Essa URL é exclusiva para depuração e não aparece na documentação publicada nem na exportação OpenAPI.

Para aprofundar o tema, consulte o artigo sobre webhooks no design de API.

Torne o teste mais robusto

Depois de validar o caminho feliz, adicione verificações que protejam o sistema contra regressões reais.

1. Correlacione o evento com a execução atual

Não consulte apenas o evento mais recente por tipo. Isso pode capturar um registro deixado por uma execução anterior.

Prefira filtrar pelo event_id, pelo ID do pedido ou por uma chave de correlação inserida nos metadados do Payment Intent:

SELECT event_id, type, payload, handled_at
FROM stripe_event_logs
WHERE event_id = :stripe_event_id
LIMIT 1;
Enter fullscreen mode Exit fullscreen mode

Ou, se você controla os metadados:

SELECT event_id, type, payload, handled_at
FROM stripe_event_logs
WHERE payload->'metadata'->>'order_id' = :order_id
  AND type = 'payment_intent.succeeded'
ORDER BY handled_at DESC
LIMIT 1;
Enter fullscreen mode Exit fullscreen mode

2. Limpe ou isole os dados de teste

Para evitar interferência entre execuções:

  • Use um banco de dados dedicado para testes.
  • Gere IDs únicos por execução.
  • Remova registros de teste ao fim do cenário.
  • Filtre consultas por uma chave de correlação.

3. Teste falhas e rejeições

Não valide somente o fluxo de sucesso. Inclua casos como:

  • Assinatura inválida.
  • Tipo de evento inesperado.
  • Pedido inexistente.
  • Falha ao atualizar o pedido.
  • Reentrega do mesmo evento.

Para uma assinatura inválida, por exemplo, o comportamento esperado pode ser:

HTTP 400
Nenhum registro de processamento concluído
Nenhum pedido marcado como pago
Enter fullscreen mode Exit fullscreen mode

As melhores práticas de webhooks de pagamento ajudam a estruturar testes para idempotência, retentativas e tratamento de erro.

4. Prove o resultado de negócio

“O evento chegou” não é a mesma coisa que “o pagamento foi aplicado”.

Sempre que possível, valide a tabela ou serviço alterado pelo webhook:

SELECT status
FROM orders
WHERE id = :order_id;
Enter fullscreen mode Exit fullscreen mode

A asserção deve provar o resultado esperado:

status = 'paid'
Enter fullscreen mode Exit fullscreen mode

Automatize o cenário com a CLI do Apidog

Quando o cenário estiver salvo no Apidog, execute-o no pipeline usando a CLI.

Instale e autentique:

npm install -g apidog-cli
apidog login --with-token <YOUR_ACCESS_TOKEN>
Enter fullscreen mode Exit fullscreen mode

Execute o cenário contra o ambiente correto:

apidog run --access-token $APIDOG_ACCESS_TOKEN -t <SCENARIO_ID> -e <ENV_ID> -r cli
Enter fullscreen mode Exit fullscreen mode

Parâmetros principais:

  • -t: ID do cenário de teste.
  • -e: ID do ambiente.
  • -r: formato do relatório.

Para gerar saída de console e relatório HTML:

apidog run \
  --access-token $APIDOG_ACCESS_TOKEN \
  -t <SCENARIO_ID> \
  -e <ENV_ID> \
  -r html,cli
Enter fullscreen mode Exit fullscreen mode

Se qualquer asserção falhar, a execução deve retornar um código diferente de zero. Assim, o pipeline pode bloquear o merge ou o deploy.

Para configurar a CLI, consulte o guia de instalação da CLI do Apidog. Para integrar ao GitHub Actions, veja a explicação sobre pipeline de CI/CD.

Perguntas frequentes

O Apidog pode receber um webhook do Stripe diretamente?

Não. O Apidog não suporta nativamente a escuta de webhooks de entrada. Capture o evento no seu backend, registre-o no banco e use um Processador Pós-Requisição para consultar e validar o resultado.

Para desenvolvimento local, encaminhe eventos com Stripe CLI ou Ngrok.

Onde as asserções acontecem?

No Processador Pós-Requisição do cenário de teste. Ele consulta a tabela de logs por meio da conexão de banco configurada no ambiente e compara os resultados com os valores esperados.

Preciso validar apenas a tabela de logs?

Não. Validar o log confirma que o evento foi persistido, mas a melhor cobertura vem de validar também o efeito de negócio, como o status do pedido na tabela orders.

Como lidar com o atraso entre o disparo e a entrega?

Use uma pequena espera ou polling com retentativas. O teste deve aguardar por tempo limitado até que o evento apareça no banco antes de falhar.

Se você está começando a testar fluxos assíncronos, leia primeiro o guia sobre como testar webhooks.

O recurso nativo de Webhook do Apidog ajuda nesse caso?

Não para receber eventos do Stripe. Ele serve para definir e documentar webhooks de saída do seu próprio sistema, enquanto este artigo trata da validação de webhooks de entrada com captura e consulta.

Conclusão

Você não aponta o Stripe para o Apidog para capturar eventos ao vivo. O fluxo suportado é:

  1. Receba o webhook no seu backend.
  2. Verifique a assinatura.
  3. Persista o evento em stripe_event_logs.
  4. Consulte o registro com o Processador Pós-Requisição do Apidog.
  5. Valide o evento e a alteração de negócio.
  6. Execute o cenário com apidog run no CI.

Com esse padrão, cada merge pode comprovar que um payment_intent.succeeded não apenas chegou ao sistema, mas também marcou o pedido correto como pago.

Top comments (0)