template-saas/docs/01-modulos/SAAS-010-webhooks.md
rckrdmrd 4dafffa386 feat: Add superadmin metrics, onboarding and module documentation
- Add MetricsPage and useOnboarding hook
- Update superadmin controller and service
- Add module documentation (docs/01-modulos/)
- Add CONTEXT-MAP.yml and Sprint 5 execution report
- Update project status and task traces

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 05:40:26 -06:00

5.7 KiB

SAAS-010: Webhooks

Metadata

  • Codigo: SAAS-010
  • Modulo: Webhooks
  • Prioridad: P2
  • Estado: Pendiente
  • Fase: 5 - Integraciones

Descripcion

Sistema de webhooks outbound: configuracion de endpoints por tenant, eventos suscribibles, firma de payloads, reintentos automaticos, y logs de entregas.

Objetivos

  1. Configuracion de webhooks por tenant
  2. Eventos suscribibles
  3. Firma HMAC de payloads
  4. Reintentos con backoff
  5. Dashboard de entregas

Alcance

Incluido

  • CRUD de webhooks por tenant
  • Eventos de sistema suscribibles
  • Firma HMAC-SHA256
  • Reintentos exponenciales (max 5)
  • Logs de entregas
  • Test endpoint

Excluido

  • Transformacion de payloads
  • Webhooks inbound (recibir)
  • Fanout a multiples endpoints por evento

Modelo de Datos

Tablas (schema: webhooks)

webhooks

  • id, tenant_id, name
  • url, secret (encrypted)
  • events (JSONB array)
  • headers (JSONB)
  • is_active, created_at

webhook_deliveries

  • id, webhook_id, event_type
  • payload (JSONB)
  • response_status, response_body
  • attempt, next_retry_at
  • delivered_at, created_at

Eventos Disponibles

Evento Descripcion Payload
user.created Usuario creado User object
user.updated Usuario actualizado User + changes
user.deleted Usuario eliminado { userId }
subscription.created Nueva suscripcion Subscription
subscription.updated Suscripcion cambiada Subscription
subscription.cancelled Suscripcion cancelada Subscription
invoice.paid Factura pagada Invoice
invoice.failed Pago fallido Invoice

Endpoints API

Metodo Endpoint Descripcion
GET /webhooks Listar webhooks
GET /webhooks/:id Obtener webhook
POST /webhooks Crear webhook
PUT /webhooks/:id Actualizar webhook
DELETE /webhooks/:id Eliminar webhook
POST /webhooks/:id/test Enviar test
GET /webhooks/:id/deliveries Historial entregas
POST /webhooks/:id/deliveries/:did/retry Reintentar
GET /webhooks/events Eventos disponibles

Firma de Payloads

Generacion

function signPayload(payload: object, secret: string): string {
  const timestamp = Date.now();
  const body = JSON.stringify(payload);
  const signature = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${body}`)
    .digest('hex');

  return `t=${timestamp},v1=${signature}`;
}

Headers Enviados

X-Webhook-Signature: t=1704067200000,v1=abc123...
X-Webhook-Id: wh_123456
X-Webhook-Event: user.created
X-Webhook-Timestamp: 1704067200000

Verificacion (lado receptor)

function verifySignature(payload: string, signature: string, secret: string): boolean {
  const [timestamp, hash] = parseSignature(signature);

  // Verificar timestamp (5 min tolerance)
  if (Date.now() - timestamp > 300000) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${payload}`)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(hash),
    Buffer.from(expected)
  );
}

Logica de Reintentos

Intento 1: Inmediato
Intento 2: +1 minuto
Intento 3: +5 minutos
Intento 4: +30 minutos
Intento 5: +2 horas
Despues: Marcar como fallido

Status de Entrega

Status Descripcion
pending En cola
delivered Entregado (2xx)
failed Fallo permanente
retrying En reintento

Implementacion

Dispatcher

@Injectable()
export class WebhookDispatcher {
  async dispatch(tenantId: string, event: string, data: object): Promise<void> {
    const webhooks = await this.getActiveWebhooks(tenantId, event);

    for (const webhook of webhooks) {
      await this.webhookQueue.add('deliver', {
        webhookId: webhook.id,
        event,
        payload: data,
        attempt: 1
      });
    }
  }
}

Worker

@Processor('webhooks')
export class WebhookWorker {
  @Process('deliver')
  async deliver(job: Job<WebhookJob>): Promise<void> {
    const { webhookId, event, payload, attempt } = job.data;
    const webhook = await this.getWebhook(webhookId);

    const signature = this.signPayload(payload, webhook.secret);

    try {
      const response = await axios.post(webhook.url, payload, {
        headers: {
          'X-Webhook-Signature': signature,
          'X-Webhook-Event': event,
          ...webhook.headers
        },
        timeout: 30000
      });

      await this.logDelivery(webhookId, event, payload, response, 'delivered');
    } catch (error) {
      await this.handleFailure(job, error);
    }
  }
}

Limites por Plan

Plan Webhooks Eventos/mes
Free 0 0
Starter 0 0
Pro 5 10,000
Enterprise 20 100,000

Entregables

Entregable Estado Archivo
webhooks.module.ts Pendiente modules/webhooks/
webhook.service.ts Pendiente services/
webhook.dispatcher.ts Pendiente services/
webhook.worker.ts Pendiente workers/
DDL webhooks schema Pendiente ddl/schemas/webhooks/

Dependencias

Depende de

  • SAAS-002 (Tenants)
  • SAAS-005 (Plans - feature flag)
  • BullMQ para queue

Bloquea a

  • Integraciones de terceros
  • Automatizaciones externas

Criterios de Aceptacion

  • CRUD webhooks funciona
  • Eventos se disparan
  • Firma es correcta
  • Reintentos funcionan
  • Test endpoint funciona
  • Logs se registran

Seguridad

  • Secrets encriptados en BD
  • HTTPS requerido para URLs
  • Timeout de 30 segundos
  • Rate limit por tenant
  • No seguir redirects

Ultima actualizacion: 2026-01-07