Pular para o conteúdo principal

Webhooks Engine

O Building Block Webhooks Engine fornece funcionalidades completas para notificação de eventos em tempo real via webhooks na Catalisa Platform.

Visão Geral

O Webhooks Engine é um módulo tenant-scoped, ou seja, requer um token com organizationId. Ele é responsável por:

  • Subscriptions - Configuração de endpoints para receber eventos
  • Signing Keys - Chaves para verificação de assinatura das mensagens
  • Delivery Logs - Registro de todas as tentativas de entrega
  • Retry Policy - Política de reenvio automático em caso de falha
  • Event Filtering - Filtros para selecionar tipos de eventos específicos

Base URL

https://api.catalisa.io/webhooks

Recursos

RecursoDescrição
SubscriptionsGerenciamento de subscriptions

Permissões

PermissãoDescrição
WEBHOOKS_CREATECriar webhook subscriptions
WEBHOOKS_READListar e visualizar subscriptions
WEBHOOKS_UPDATEAtualizar subscriptions
WEBHOOKS_DELETEExcluir subscriptions
WEBHOOKS_MANAGE_KEYSGerenciar signing keys
WEBHOOKS_VIEW_LOGSVisualizar logs de entrega
WEBHOOKS_TESTTestar webhook endpoints
WEBHOOKS_CONFIGGerenciar configuração global

Exemplo Rápido

Fluxo completo para configurar um webhook:

// 1. Criar uma subscription de webhook
const subscriptionResponse = await fetch('https://api.catalisa.io/webhooks/api/v1/subscriptions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
data: {
type: 'webhook-subscriptions',
attributes: {
name: 'Notificações de Billing',
endpointUrl: 'https://minha-app.com/webhooks/billing',
eventFilters: [
'billing.invoice.created',
'billing.invoice.paid',
'billing.payment.completed',
],
timeoutMs: 30000,
retryConfig: {
maxRetries: 5,
retryBackoffMs: 1000,
retryBackoffMultiplier: 2.0,
},
},
},
}),
});

const { data: subscription } = await subscriptionResponse.json();
console.log(`Subscription criada: ${subscription.id}`);

// 2. Obter as signing keys para verificação
const keysResponse = await fetch('https://api.catalisa.io/webhooks/api/v1/signing-keys', {
headers: {
'Authorization': `Bearer ${token}`,
},
});

const { data: keys } = await keysResponse.json();
const activeKey = keys.find(k => k.attributes.status === 'ACTIVE');
console.log(`Key ID para verificação: ${activeKey.attributes.keyId}`);

// 3. Testar o endpoint
const testResponse = await fetch(`https://api.catalisa.io/webhooks/api/v1/subscriptions/${subscription.id}/test`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
});

const { data: testResult } = await testResponse.json();
if (testResult.attributes.success) {
console.log(`Teste bem-sucedido! Tempo de resposta: ${testResult.attributes.responseTimeMs}ms`);
} else {
console.log(`Teste falhou: ${testResult.attributes.errorMessage}`);
}

// 4. Verificar logs de entrega
const logsResponse = await fetch(`https://api.catalisa.io/webhooks/api/v1/delivery-logs?filter[subscriptionId]=${subscription.id}`, {
headers: {
'Authorization': `Bearer ${token}`,
},
});

const { data: logs } = await logsResponse.json();
logs.forEach(log => {
console.log(`${log.attributes.eventType}: ${log.attributes.status}`);
});

Arquitetura

┌─────────────────────────────────────────────────────────────────────┐
│ Webhooks Engine │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Event Publisher │ │
│ │ │ │
│ │ Redis Pub/Sub ──► Event Router ──► Delivery Queue │ │
│ │ │ │ │
│ │ ┌──────▼──────┐ │ │
│ │ │ Filter │ │ │
│ │ │ Events │ │ │
│ │ └─────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Subscriptions│ │ Signing │ │ Delivery │ │
│ │ │ │ Keys │ │ Logs │ │
│ │ - endpoint │ │ │ │ │ │
│ │ - filters │ │ - ED25519 │ │ - attempts │ │
│ │ - retry │ │ - rotation │ │ - status │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Delivery Worker │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Sign │ │ Send │ │ Handle │ │ │
│ │ │ Request │ ──►│ HTTP │ ──►│ Response │ │ │
│ │ │ │ │ Request │ │ / Retry │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

Conceitos Importantes

Status da Subscription

StatusDescrição
ACTIVESubscription ativa, recebendo eventos
PAUSEDSubscription pausada temporariamente
DISABLEDSubscription desabilitada (muitas falhas)

Retry Policy

O Webhooks Engine implementa uma política de retry com backoff exponencial:

ConfiguraçãoPadrãoDescrição
maxRetries5Número máximo de tentativas
retryBackoffMs1000Tempo inicial de espera (ms)
retryBackoffMultiplier2.0Multiplicador do backoff

Exemplo de intervalos com configuração padrão:

  • 1ª tentativa: imediata
  • 2ª tentativa: 1 segundo depois
  • 3ª tentativa: 2 segundos depois
  • 4ª tentativa: 4 segundos depois
  • 5ª tentativa: 8 segundos depois
  • 6ª tentativa: 16 segundos depois

Limite máximo: 1 hora entre tentativas (para evitar delays muito longos)

Status de Entrega

StatusDescrição
PENDINGAguardando primeira tentativa
DELIVEREDEntregue com sucesso (2xx)
FAILEDFalhou após todas as tentativas
RETRYINGAguardando próxima tentativa

Assinatura das Mensagens

Todas as requisições enviadas incluem headers de assinatura para verificação:

HeaderDescrição
x-webhook-idID único da entrega
x-webhook-timestampTimestamp da requisição (ISO 8601)
x-webhook-signatureAssinatura ED25519 em base64
x-webhook-key-idID da chave usada
x-webhook-versionVersão do protocolo (v1)

Verificação da Assinatura

Para verificar a autenticidade de uma mensagem:

const crypto = require('crypto');

function verifyWebhookSignature(payload, headers, publicKey) {
const message = `${headers['x-webhook-id']}.${headers['x-webhook-timestamp']}.${JSON.stringify(payload)}`;

const signature = Buffer.from(headers['x-webhook-signature'], 'base64');
const key = Buffer.from(publicKey, 'base64');

return crypto.verify(
null, // ED25519 não usa algoritmo de hash
Buffer.from(message),
{ key, format: 'der', type: 'spki' },
signature
);
}

// Uso no handler do webhook
app.post('/webhooks/billing', (req, res) => {
const isValid = verifyWebhookSignature(req.body, req.headers, PUBLIC_KEY);

if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}

// Processar evento
console.log(`Evento recebido: ${req.body.type}`);
res.status(200).json({ received: true });
});

Formato do Payload

{
"id": "evt_550e8400-e29b-41d4-a716-446655440070",
"type": "billing.invoice.paid",
"data": {
"invoiceId": "550e8400-e29b-41d4-a716-446655440050",
"billingAccountId": "550e8400-e29b-41d4-a716-446655440010",
"amount": 1499.50,
"currency": "BRL",
"paidAt": "2024-02-28T14:00:00Z"
},
"metadata": {
"timestamp": "2024-02-28T14:00:05Z",
"correlationId": "req_550e8400-e29b-41d4-a716-446655440080",
"organizationId": "550e8400-e29b-41d4-a716-446655440000"
}
}

Tipos de Eventos Disponíveis

IAM

  • iam.user.created, iam.user.updated, iam.user.deleted
  • iam.organization.created, iam.organization.updated
  • iam.role.created, iam.role.updated, iam.role.deleted

Billing

  • billing.account.created, billing.account.updated
  • billing.subscription.created, billing.subscription.activated
  • billing.subscription.paused, billing.subscription.canceled
  • billing.invoice.created, billing.invoice.finalized, billing.invoice.paid
  • billing.payment.completed, billing.payment.refunded

Customers

  • customers.person.created, customers.person.updated, customers.person.deleted

Products

  • products.product.created, products.product.updated, products.product.deleted

Decision Engine

  • decision.executed

Feature Flags

  • feature-flags.flag.created, feature-flags.flag.enabled, feature-flags.flag.disabled

Boas Práticas

  1. Responda rapidamente - Retorne 2xx em até 30 segundos
  2. Processe de forma assíncrona - Coloque eventos em uma fila
  3. Idempotência - Use o id do evento para evitar duplicação
  4. Verifique a assinatura - Sempre valide antes de processar
  5. Monitore os logs - Acompanhe falhas de entrega
  6. Use filtros - Configure apenas os eventos necessários