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:
| Gateway | URL do Webhook |
|---|---|
| Stripe | https://api.toktus.com/api/v1/webhooks/stripe |
| Pagar.me | https://api.toktus.com/api/v1/webhooks/pagarme |
| Asaas | https://api.toktus.com/api/v1/webhooks/asaas |
| Mercado Pago | https://api.toktus.com/api/v1/webhooks/mercadopago |
| PagSeguro | https://api.toktus.com/api/v1/webhooks/pagseguro |
[!NOTE]
Estes endpoints não requerem headerx-api-key— a validação é feita internamente por cada gateway.
Configuração por Gateway
Stripe
- Acesse Dashboard Stripe → Webhooks
- Clique em Add endpoint
- Informe a URL:
https://api.toktus.com/api/v1/webhooks/stripe - Selecione os eventos desejados:
payment_intent.succeeded— Pagamento confirmadopayment_intent.payment_failed— Pagamento falhoucharge.refunded— Cobrança estornadacustomer.subscription.created— Assinatura criadacustomer.subscription.updated— Assinatura atualizadacustomer.subscription.deleted— Assinatura cancelada
- 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.succeededPagar.me
- Acesse o painel Pagar.me
- Configurações → Webhooks
- Adicione a URL:
https://api.toktus.com/api/v1/webhooks/pagarme - Selecione os eventos:
order.paid— Pedido pagoorder.payment_failed— Pagamento falhoucharge.refunded— Cobrança estornadasubscription.created— Assinatura criadasubscription.canceled— Assinatura cancelada
Asaas
- Acesse o Painel Asaas
- Integrações → Webhooks
- Adicione a URL:
https://api.toktus.com/api/v1/webhooks/asaas - Selecione os eventos:
PAYMENT_RECEIVED— Pagamento recebidoPAYMENT_CONFIRMED— Pagamento confirmadoPAYMENT_OVERDUE— Cobrança vencidaPAYMENT_REFUND_REQUESTED— Estorno solicitadoPAYMENT_DELETED— Cobrança cancelada
Mercado Pago
- Acesse Mercado Pago Developers
- Suas Integrações → Webhooks
- Adicione a URL:
https://api.toktus.com/api/v1/webhooks/mercadopago - Selecione os tópicos:
- Payments — Pagamentos
- Plans — Planos
- Subscriptions — Assinaturas
PagSeguro
- Acesse Painel PagBank
- Configurações → URL de Notificação
- Informe:
https://api.toktus.com/api/v1/webhooks/pagseguro - 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:
- Assinatura HMAC (Stripe, Mercado Pago)
- Token de autenticação (Asaas)
- IP de origem (PagSeguro)
- 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:
| Gateway | Tentativas | Intervalo |
|---|---|---|
| Stripe | Até 3 dias | Exponencial (1h, 2h, 4h...) |
| Pagar.me | 10 tentativas | Crescente |
| Asaas | 5 tentativas | Crescente |
| Mercado Pago | Até 24h | Variável |
| PagSeguro | 3 tentativas | 10 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 gatewayStripe CLI
stripe listen --forward-to localhost:3000/webhook
stripe trigger payment_intent.succeededWebhook.site
Use webhook.site para inspecionar payloads antes de implementar.
Eventos Comuns
Pagamentos
| Evento | Stripe | Pagar.me | Asaas | Mercado Pago | PagSeguro |
|---|---|---|---|---|---|
| Pagamento aprovado | payment_intent.succeeded | order.paid | PAYMENT_RECEIVED | payment.updated | Transação aprovada |
| Pagamento falhou | payment_intent.payment_failed | order.payment_failed | PAYMENT_OVERDUE | payment.updated | - |
| Estorno criado | charge.refunded | charge.refunded | PAYMENT_REFUND_REQUESTED | payment.updated | Transação estornada |
Assinaturas
| Evento | Stripe | Pagar.me | Asaas | Mercado Pago | PagSeguro |
|---|---|---|---|---|---|
| Criada | customer.subscription.created | subscription.created | - | subscription.created | - |
| Cancelada | customer.subscription.deleted | subscription.canceled | - | subscription.updated | - |
| Falha na renovação | invoice.payment_failed | - | PAYMENT_OVERDUE | - | - |
Updated about 2 hours ago