Pular para o conteúdo principal

O que são Webhooks?

Webhooks são callbacks HTTP que a KillB envia para seu servidor quando eventos ocorrem. Eles habilitam notificações em tempo real sobre ramps, usuários, contas e transações sem polling constante.
Webhooks são a forma recomendada de monitorar status de transações e manter sua aplicação sincronizada com a KillB.

Como os Webhooks Funcionam

Estrutura do Evento Webhook

Todos os eventos webhook seguem esta estrutura:
{
  id: string,              // UUID - ID único do evento
  event: string,           // Tipo de evento: RAMP | USER | TRANSACTION | ACCOUNT | CUSTODIAL_ACCOUNT
  action: string,          // Ação: CREATE | UPDATE | DELETE
  data: object,            // Dados específicos do evento
  createdAt: string,       // Timestamp ISO 8601
  updatedAt: string,       // Timestamp ISO 8601
  attempts: number         // Contagem de tentativas de entrega (0 a 5)
}

Eventos de Webhook

A KillB suporta os seguintes tipos de eventos:
Eventos de transação rampTipo de Evento: RAMPAções:
  • CREATE - Novo ramp criado
  • UPDATE - Status do ramp mudou
  • DELETE - Ramp cancelado (raro)
Valores de Status Comuns:
  • QUOTE_CREATED - Cotação criada
  • PENDING_PAYMENT - Aguardando pagamento
  • CASH_IN_PROCESSING - Pagamento sendo processado
  • CASH_IN_COMPLETED - Pagamento confirmado
  • PROCESSING - Convertendo moeda
  • CASH_OUT_PROCESSING - Enviando fundos
  • COMPLETED - Ramp concluído
  • FAILED - Ramp falhou
  • CANCELED - Ramp cancelado

Configurando Webhooks

Criar Configuração de Webhook

POST /api/v2/webhooks
Requisição:
{
  "url": "https://api.seuapp.com/webhooks/killb",
  "secret": "sua-chave-secreta-min-32-caracteres",
  "events": ["RAMP", "USER", "ACCOUNT"]
}
Resposta:
{
  "id": "webhook-id",
  "customerId": "customer-id",
  "url": "https://api.seuapp.com/webhooks/killb",
  "events": ["RAMP", "USER", "ACCOUNT"],
  "active": true,
  "createdAt": "2024-01-15T10:30:00.000Z"
}
const setupWebhook = async () => {
  const response = await fetch('https://teste-94u93qnn.uc.gateway.dev/api/v2/webhooks', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url: 'https://api.seuapp.com/webhooks/killb',
      secret: process.env.WEBHOOK_SECRET,
      events: ['RAMP', 'USER', 'ACCOUNT', 'TRANSACTION']
    })
  });
  
  return await response.json();
};

Segurança de Webhook

Verificação de Assinatura

A KillB assina todas as requisições webhook com HMAC SHA-256. Sempre verifique assinaturas para garantir autenticidade. Cabeçalho: x-signature-sha256
const crypto = require('crypto');
const express = require('express');

app.post('/webhooks/killb', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-signature-sha256'];
  const payload = req.body.toString();
  
  // Calcular assinatura esperada
  const expectedSignature = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');
  
  // Verificar assinatura
  if (signature !== expectedSignature) {
    console.error('Assinatura de webhook inválida');
    return res.status(401).json({ error: 'Assinatura inválida' });
  }
  
  // Analisar e processar evento
  const event = JSON.parse(payload);
  processWebhookEvent(event);
  
  res.status(200).json({ received: true });
});
Nunca pule a verificação de assinatura! Webhooks não verificados podem ser falsificados por atacantes.

Requisitos de Webhook

Seu endpoint de webhook deve:
  • Retornar 200 OK dentro de 5 segundos
  • Processar eventos assincronamente
  • Não esperar por operações lentas
  • Usar filas de jobs para processamento pesado
  • Ser idempotente (lidar com eventos duplicados)
  • Usar ID do evento para detectar duplicatas
  • Armazenar IDs de eventos processados
  • Pular eventos já processados
  • Manter >99% de uptime
  • Usar apenas HTTPS
  • Ter certificado SSL válido
  • Responder com códigos de status adequados
  • Sempre verificar x-signature-sha256
  • Usar comparação de tempo constante
  • Rejeitar assinaturas inválidas
  • Registrar falhas de verificação

Lógica de Retry

A KillB automaticamente tenta novamente entregas de webhook falhadas com backoff exponencial: Agenda de Retry:
  • Tentativa 0: Entrega imediata
  • Tentativa 1: Após 1 minuto
  • Tentativa 2: Após 5 minutos
  • Tentativa 3: Após 15 minutos
  • Tentativa 4: Após 1 hora
  • Tentativa 5: Após 6 horas (tentativa final)
Critérios de Falha:
  • Código de resposta não-2xx
  • Timeout de conexão (> 5 segundos)
  • Erro de rede
O campo attempts no payload do webhook indica a tentativa de entrega atual (0 a 5).
Após a tentativa 5 falhar, o webhook é marcado como permanentemente falhado e requer intervenção manual. Verifique seus logs de webhook e corrija quaisquer problemas.

Testando Webhooks

Teste Local com ngrok

# Instalar ngrok
npm install -g ngrok

# Iniciar seu servidor local
node server.js

# Expor para internet
ngrok http 3000

# Usar URL ngrok na configuração de webhook
# https://abc123.ngrok.io/webhooks/killb

Teste Manual

Acione eventos de teste no sandbox:
// Criar ramp de teste
const ramp = await createRamp(quoteId, userId, accountId);

// Simular conclusão (aciona webhooks)
await fetch('/api/v2/faker/cash-in', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}` },
  body: JSON.stringify({ rampId: ramp.id })
});

// Seu webhook deve receber evento ramp.cash_in_completed

Processamento de Eventos

Exemplo de Handler

const processWebhookEvent = (webhookEvent) => {
  const { id, event, action, data, attempts } = webhookEvent;
  
  console.log(`Processando ${event}.${action} (tentativa ${attempts})`);
  
  // Rotear baseado no tipo de evento e ação
  switch(event) {
    case 'RAMP':
      handleRampEvent(action, data);
      break;
      
    case 'USER':
      handleUserEvent(action, data);
      break;
      
    case 'ACCOUNT':
      handleAccountEvent(action, data);
      break;
      
    case 'TRANSACTION':
      handleTransactionEvent(action, data);
      break;
      
    default:
      console.log(`Tipo de evento não tratado: ${event}`);
  }
};

const handleRampEvent = async (action, data) => {
  if (action === 'UPDATE' && data.status === 'COMPLETED') {
    // Atualizar banco de dados
    await db.ramps.update(data.id, {
      status: data.status,
      updatedAt: data.updatedAt,
      transferProof: data.transferProof
    });
    
    // Notificar usuário
    await sendEmail(data.userId, 'Transação Concluída', {
      amount: data.toAmount,
      currency: data.toCurrency,
      txHash: data.transferProof
    });
  }
};

Idempotência

Lide com entregas de webhook duplicadas usando o ID do evento:
const handleWebhook = async (webhookEvent) => {
  const { id, event, action, data } = webhookEvent;
  
  // Verificar se já processado (usando ID único do evento)
  const existing = await db.webhookEvents.findOne({ eventId: id });
  
  if (existing) {
    console.log(`Evento duplicado ${id}, pulando`);
    return { received: true, duplicate: true };
  }
  
  // Armazenar evento imediatamente para prevenir processamento duplicado
  await db.webhookEvents.create({
    eventId: id,
    eventType: event,
    action: action,
    payload: data,
    receivedAt: new Date(),
    processed: false
  });
  
  try {
    // Processar evento
    await processWebhookEvent(webhookEvent);
    
    // Marcar como processado
    await db.webhookEvents.update(
      { eventId: id },
      { processed: true, processedAt: new Date() }
    );
    
    return { received: true };
  } catch (error) {
    // Registrar erro mas ainda marcar como recebido
    await db.webhookEvents.update(
      { eventId: id },
      { error: error.message, failedAt: new Date() }
    );
    
    throw error;
  }
};

Melhores Práticas

app.post('/webhooks/killb', async (req, res) => {
  // Verificar assinatura
  verifySignature(req);
  
  // Reconhecer imediatamente
  res.status(200).json({ received: true });
  
  // Processar assincronamente
  processAsync(req.body);
});
  • Retornar 200 OK imediatamente
  • Processar eventos em background
  • Usar filas de jobs (Bull, Celery, etc.)
  • Não esperar por serviços externos
const processWebhook = async (event) => {
  try {
    await processEvent(event);
  } catch (error) {
    console.error('Processamento de webhook falhou:', error);
    // Registrar em serviço de monitoramento
    await logError(error, event);
    // Não lançar - já processamos
    // KillB tentará novamente de qualquer forma
  }
};
  • Capturar todos os erros
  • Registrar falhas
  • Não lançar após reconhecer
  • Monitorar taxas de erro
await db.webhookLogs.create({
  eventId: event.id,
  eventType: event.event,
  payload: event.data,
  receivedAt: new Date(),
  processed: true
});
  • Manter trilha de auditoria
  • Habilitar replay se necessário
  • Depurar problemas facilmente
  • Atender requisitos de compliance

Próximos Passos