michangarrito/docs/02-integraciones/INT-014-webhooks-outbound.md
rckrdmrd 2c916e75e5 [SIMCO-V4] feat: Agregar documentación SaaS, ADRs e integraciones
Nuevas Épicas (MCH-029 a MCH-033):
- Infraestructura SaaS multi-tenant
- Auth Social (OAuth2)
- Auditoría Empresarial
- Feature Flags
- Onboarding Wizard

Nuevas Integraciones (INT-010 a INT-014):
- Email Providers (SendGrid, Mailgun, SES)
- Storage Cloud (S3, GCS, Azure)
- OAuth Social
- Redis Cache
- Webhooks Outbound

Nuevos ADRs (0004 a 0011):
- Notifications Realtime
- Feature Flags Strategy
- Storage Abstraction
- Webhook Retry Strategy
- Audit Log Retention
- Rate Limiting
- OAuth Social Implementation
- Email Multi-provider

Actualizados:
- MASTER_INVENTORY.yml
- CONTEXT-MAP.yml
- HERENCIA-SIMCO.md
- Mapas de documentación

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 01:43:15 -06:00

10 KiB

id type title provider status integration_type created_at updated_at simco_version tags
INT-014 Integration Webhooks Outbound BullMQ Planificado events 2026-01-10 2026-01-10 4.0.1
webhooks
events
bullmq
integration
outbound

INT-014: Webhooks Outbound

Metadata

Campo Valor
Codigo INT-014
Proveedor BullMQ (interno)
Tipo Eventos
Estado Planificado
Multi-tenant Si
Epic Relacionada MCH-029
Owner Backend Team

1. Descripcion

Sistema de webhooks outbound que permite a tenants recibir notificaciones HTTP cuando ocurren eventos en el sistema. Incluye firma de payloads, reintentos con backoff y logs de entrega.

Casos de uso principales:

  • Notificar sistemas externos de nuevos pedidos
  • Sincronizar inventario con ERP
  • Integraciones con Zapier/Make
  • Alertas personalizadas

2. Eventos Disponibles

Eventos por Categoria

Categoria Evento Payload
Orders order.created Order completa
order.updated Order con cambios
order.completed Order finalizada
order.cancelled Order cancelada
Products product.created Producto nuevo
product.updated Producto modificado
product.deleted Producto eliminado
product.low_stock Stock bajo minimo
Payments payment.received Pago recibido
payment.failed Pago fallido
payment.refunded Pago reembolsado
Customers customer.created Cliente nuevo
customer.updated Cliente modificado

3. Configuracion de Endpoints

API de Configuracion

// POST /api/webhooks/endpoints
{
  "url": "https://example.com/webhook",
  "events": ["order.created", "payment.received"],
  "secret": "whsec_xxxxxxxx", // Generado si no se provee
  "description": "Mi integracion",
  "is_active": true
}

// Response
{
  "id": "wh_123abc",
  "url": "https://example.com/webhook",
  "events": ["order.created", "payment.received"],
  "secret": "whsec_xxxxxxxx",
  "is_active": true,
  "created_at": "2026-01-10T10:00:00Z"
}

Endpoints CRUD

Metodo Ruta Descripcion
GET /api/webhooks/endpoints Listar endpoints
POST /api/webhooks/endpoints Crear endpoint
GET /api/webhooks/endpoints/:id Obtener endpoint
PATCH /api/webhooks/endpoints/:id Actualizar endpoint
DELETE /api/webhooks/endpoints/:id Eliminar endpoint
GET /api/webhooks/deliveries Listar entregas
POST /api/webhooks/endpoints/:id/test Enviar test

4. Payload de Webhook

Estructura

{
  "id": "evt_123abc",
  "type": "order.created",
  "created_at": "2026-01-10T10:00:00Z",
  "data": {
    "id": "ord_456def",
    "total": 150.00,
    "items": [...],
    "customer": {...}
  },
  "tenant_id": "550e8400-e29b-41d4-a716-446655440000"
}

Headers

Header Valor Descripcion
Content-Type application/json Tipo de contenido
X-Webhook-Id evt_123abc ID del evento
X-Webhook-Timestamp 1704880800 Unix timestamp
X-Webhook-Signature sha256=xxx Firma HMAC
User-Agent MiChangarrito/1.0 Identificador

5. Firma de Payloads

Generacion de Firma

function signPayload(
  payload: string,
  secret: string,
  timestamp: number,
): string {
  const signedPayload = `${timestamp}.${payload}`;
  const signature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return `sha256=${signature}`;
}

Verificacion en Cliente

// Ejemplo para el receptor del webhook
function verifySignature(
  payload: string,
  signature: string,
  secret: string,
  timestamp: number,
  tolerance: number = 300, // 5 minutos
): boolean {
  const currentTime = Math.floor(Date.now() / 1000);

  // Verificar que no sea muy viejo
  if (currentTime - timestamp > tolerance) {
    throw new Error('Timestamp too old');
  }

  const expected = signPayload(payload, secret, timestamp);
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
}

6. Estrategia de Reintentos

Exponential Backoff

Intento 1: Inmediato
Intento 2: 1 segundo
Intento 3: 2 segundos
Intento 4: 4 segundos
Intento 5: 8 segundos
Intento 6: 16 segundos (maximo)

Configuracion BullMQ

await this.webhookQueue.add('deliver', {
  endpointId: endpoint.id,
  eventId: event.id,
  payload: event.data,
}, {
  attempts: 6,
  backoff: {
    type: 'exponential',
    delay: 1000,
  },
  removeOnComplete: true,
  removeOnFail: false, // Mantener para logs
});

Codigos de Respuesta

Codigo Accion Descripcion
2xx Exito Entrega exitosa
3xx Retry Seguir redirecciones
4xx Fallo No reintentar (excepto 429)
429 Retry Rate limited, esperar
5xx Retry Error del servidor
Timeout Retry Esperar siguiente intento

7. Tabla de BD

Schema webhooks

CREATE SCHEMA IF NOT EXISTS webhooks;

CREATE TABLE webhooks.endpoints (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    url VARCHAR(2000) NOT NULL,
    description VARCHAR(255),
    events TEXT[] NOT NULL,
    secret VARCHAR(255) NOT NULL,
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE webhooks.deliveries (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    endpoint_id UUID REFERENCES webhooks.endpoints(id),
    event_type VARCHAR(100) NOT NULL,
    event_id VARCHAR(100) NOT NULL,
    payload JSONB NOT NULL,
    status VARCHAR(20) NOT NULL, -- pending, success, failed
    attempts INTEGER DEFAULT 0,
    last_attempt_at TIMESTAMP WITH TIME ZONE,
    response_status INTEGER,
    response_body TEXT,
    error_message TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    completed_at TIMESTAMP WITH TIME ZONE
);

CREATE INDEX idx_deliveries_endpoint ON webhooks.deliveries(endpoint_id);
CREATE INDEX idx_deliveries_status ON webhooks.deliveries(status);

8. Procesador de Webhooks

@Processor('webhooks')
export class WebhookProcessor {
  constructor(
    private readonly httpService: HttpService,
    private readonly webhookService: WebhookService,
  ) {}

  @Process('deliver')
  async handleDelivery(job: Job<WebhookJobData>) {
    const { endpointId, eventId, payload } = job.data;

    const endpoint = await this.webhookService.getEndpoint(endpointId);
    if (!endpoint.is_active) return;

    const timestamp = Math.floor(Date.now() / 1000);
    const payloadString = JSON.stringify(payload);
    const signature = this.signPayload(payloadString, endpoint.secret, timestamp);

    try {
      const response = await this.httpService.axiosRef.post(
        endpoint.url,
        payload,
        {
          headers: {
            'Content-Type': 'application/json',
            'X-Webhook-Id': eventId,
            'X-Webhook-Timestamp': timestamp.toString(),
            'X-Webhook-Signature': signature,
            'User-Agent': 'MiChangarrito/1.0',
          },
          timeout: 30000, // 30 segundos
        },
      );

      await this.webhookService.logDelivery(endpointId, eventId, {
        status: 'success',
        responseStatus: response.status,
        attempts: job.attemptsMade + 1,
      });

    } catch (error) {
      const shouldRetry = this.shouldRetry(error);

      await this.webhookService.logDelivery(endpointId, eventId, {
        status: shouldRetry ? 'pending' : 'failed',
        responseStatus: error.response?.status,
        errorMessage: error.message,
        attempts: job.attemptsMade + 1,
      });

      if (shouldRetry) {
        throw error; // BullMQ reintentara
      }
    }
  }

  private shouldRetry(error: any): boolean {
    if (!error.response) return true; // Timeout o network error
    const status = error.response.status;
    return status === 429 || status >= 500;
  }
}

9. UI de Administracion

Funcionalidades

  • Lista de endpoints configurados
  • Crear/editar/eliminar endpoints
  • Ver historial de entregas
  • Reintentar entregas fallidas
  • Enviar evento de prueba
  • Rotar secret

Ejemplo de Vista

+--------------------------------------------------+
| Webhooks                              [+ Nuevo]  |
+--------------------------------------------------+
| URL                    | Eventos      | Estado   |
|------------------------|--------------|----------|
| https://example.com/wh | order.*      | Activo   |
| https://zapier.com/... | payment.*    | Activo   |
| https://erp.local/api  | product.*    | Inactivo |
+--------------------------------------------------+

Entregas Recientes:
+--------------------------------------------------+
| Evento          | Destino      | Estado | Fecha  |
|-----------------|--------------|--------|--------|
| order.created   | example.com  | OK     | 10:00  |
| payment.failed  | zapier.com   | OK     | 09:55  |
| product.updated | erp.local    | Failed | 09:50  |
+--------------------------------------------------+

10. Testing

Enviar Evento de Prueba

// POST /api/webhooks/endpoints/:id/test
{
  "event_type": "test.webhook"
}

// Payload enviado
{
  "id": "evt_test_123",
  "type": "test.webhook",
  "created_at": "2026-01-10T10:00:00Z",
  "data": {
    "message": "This is a test webhook"
  }
}

Herramientas de Debug


11. Monitoreo

Metricas

Metrica Descripcion Alerta
webhook_deliveries_total Total entregas -
webhook_deliveries_success Entregas exitosas -
webhook_deliveries_failed Entregas fallidas > 10%
webhook_delivery_latency Latencia de entrega > 5s
webhook_queue_length Jobs pendientes > 100

12. Referencias


Ultima actualizacion: 2026-01-10 Autor: Backend Team