Webhooks

Webhooks permitem que você receba notificações em tempo real sobre eventos que acontecem nos gateways de pagamento (pagamento confirmado, cobrança cancelada, assinatura atualizada, etc).

Como Funcionam

sequenceDiagram
    participant Gateway
    participant RootGateway
    participant SeuBackend

    Gateway->>RootGateway: POST /api/v1/webhooks/stripe
    RootGateway->>RootGateway: Valida assinatura
    RootGateway->>RootGateway: Processa evento
    RootGateway->>SeuBackend: POST /seu-webhook
    SeuBackend->>RootGateway: HTTP 200
    RootGateway->>Gateway: HTTP 200

URLs dos Webhooks

Cada gateway possui seu próprio endpoint:

GatewayURL do Webhook
Stripehttps://api.toktus.com/api/v1/webhooks/stripe
Pagar.mehttps://api.toktus.com/api/v1/webhooks/pagarme
Asaashttps://api.toktus.com/api/v1/webhooks/asaas
Mercado Pagohttps://api.toktus.com/api/v1/webhooks/mercadopago
PagSegurohttps://api.toktus.com/api/v1/webhooks/pagseguro

[!NOTE]
Estes endpoints não requerem header x-api-key — a validação é feita internamente por cada gateway.

Configuração por Gateway

Stripe

  1. Acesse Dashboard Stripe → Webhooks
  2. Clique em Add endpoint
  3. Informe a URL: https://api.toktus.com/api/v1/webhooks/stripe
  4. Selecione os eventos desejados:
    • payment_intent.succeeded — Pagamento confirmado
    • payment_intent.payment_failed — Pagamento falhou
    • charge.refunded — Cobrança estornada
    • customer.subscription.created — Assinatura criada
    • customer.subscription.updated — Assinatura atualizada
    • customer.subscription.deleted — Assinatura cancelada
  5. Copie o Signing secret (formato whsec_...)

Testar localmente com Stripe CLI:

# Instalar Stripe CLI
brew install stripe/stripe-cli/stripe

# Forward webhooks para localhost
stripe listen --forward-to localhost:3000/webhook

# Trigger evento de teste
stripe trigger payment_intent.succeeded

Pagar.me

  1. Acesse o painel Pagar.me
  2. Configurações → Webhooks
  3. Adicione a URL: https://api.toktus.com/api/v1/webhooks/pagarme
  4. Selecione os eventos:
    • order.paid — Pedido pago
    • order.payment_failed — Pagamento falhou
    • charge.refunded — Cobrança estornada
    • subscription.created — Assinatura criada
    • subscription.canceled — Assinatura cancelada

Asaas

  1. Acesse o Painel Asaas
  2. Integrações → Webhooks
  3. Adicione a URL: https://api.toktus.com/api/v1/webhooks/asaas
  4. Selecione os eventos:
    • PAYMENT_RECEIVED — Pagamento recebido
    • PAYMENT_CONFIRMED — Pagamento confirmado
    • PAYMENT_OVERDUE — Cobrança vencida
    • PAYMENT_REFUND_REQUESTED — Estorno solicitado
    • PAYMENT_DELETED — Cobrança cancelada

Mercado Pago

  1. Acesse Mercado Pago Developers
  2. Suas Integrações → Webhooks
  3. Adicione a URL: https://api.toktus.com/api/v1/webhooks/mercadopago
  4. Selecione os tópicos:
    • Payments — Pagamentos
    • Plans — Planos
    • Subscriptions — Assinaturas

PagSeguro

  1. Acesse Painel PagBank
  2. Configurações → URL de Notificação
  3. Informe: https://api.toktus.com/api/v1/webhooks/pagseguro
  4. Salve as alterações

Processamento de Webhooks

Melhores Práticas

Responda Rápido

Sempre retorne HTTP 200 o mais rápido possível. Processe a lógica de negócio de forma assíncrona:

// Avoid — synchronous processing may cause gateway timeout
app.post('/webhook', async (req, res) => {
  await processarPedido(req.body);
  await enviarEmail(req.body);
  await atualizarInventario(req.body);
  res.json({received: true});
});

// Correct — acknowledge immediately, process asynchronously
const queue = require('bull');
const webhookQueue = new queue('webhooks', process.env.REDIS_URL);

app.post('/webhook', (req, res) => {
  webhookQueue.add(req.body);
  res.json({received: true});
});

webhookQueue.process(async (job) => {
  await processarPedido(job.data);
  await enviarEmail(job.data);
  await atualizarInventario(job.data);
});

Seja Idempotente

Gateways podem enviar o mesmo evento múltiplas vezes. Use um ID único para evitar processamento duplicado:

app.post('/webhook', async (req, res) => {
  const eventId = req.body.id || req.headers['x-event-id'];

  const existe = await db.webhookEvents.findOne({eventId});
  if (existe) {
    return res.json({received: true}); // Já processado
  }

  await processar(req.body);
  await db.webhookEvents.create({eventId, processedAt: new Date()});

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

Valide Sempre

Nunca confie cegamente em webhooks recebidos. Sempre valide:

  1. Assinatura HMAC (Stripe, Mercado Pago)
  2. Token de autenticação (Asaas)
  3. IP de origem (PagSeguro)
  4. Consultar API do gateway para confirmar o evento

Log Everything

Registre todos os webhooks recebidos para auditoria:

app.post('/webhook', async (req, res) => {
  await db.webhookLogs.create({
    gateway: 'stripe',
    eventType: req.body.type,
    eventId: req.body.id,
    payload: req.body,
    headers: req.headers,
    receivedAt: new Date()
  });

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

Retry Logic dos Gateways

Se o processamento falhar, o gateway tentará reenviar:

GatewayTentativasIntervalo
StripeAté 3 diasExponencial (1h, 2h, 4h...)
Pagar.me10 tentativasCrescente
Asaas5 tentativasCrescente
Mercado PagoAté 24hVariável
PagSeguro3 tentativas10 min, 1h, 24h

Testar Webhooks Localmente

ngrok

Exponha seu localhost para receber webhooks:

# Instalar
brew install ngrok   # macOS
choco install ngrok  # Windows

# Expor porta 3000
ngrok http 3000

# Use a URL gerada no painel do gateway

Stripe CLI

stripe listen --forward-to localhost:3000/webhook
stripe trigger payment_intent.succeeded

Webhook.site

Use webhook.site para inspecionar payloads antes de implementar.

Eventos Comuns

Pagamentos

EventoStripePagar.meAsaasMercado PagoPagSeguro
Pagamento aprovadopayment_intent.succeededorder.paidPAYMENT_RECEIVEDpayment.updatedTransação aprovada
Pagamento falhoupayment_intent.payment_failedorder.payment_failedPAYMENT_OVERDUEpayment.updated-
Estorno criadocharge.refundedcharge.refundedPAYMENT_REFUND_REQUESTEDpayment.updatedTransação estornada

Assinaturas

EventoStripePagar.meAsaasMercado PagoPagSeguro
Criadacustomer.subscription.createdsubscription.created-subscription.created-
Canceladacustomer.subscription.deletedsubscription.canceled-subscription.updated-
Falha na renovaçãoinvoice.payment_failed-PAYMENT_OVERDUE--