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

430 lines
10 KiB
Markdown

---
id: INT-014
type: Integration
title: "Webhooks Outbound"
provider: "BullMQ"
status: Planificado
integration_type: "events"
created_at: 2026-01-10
updated_at: 2026-01-10
simco_version: "4.0.1"
tags:
- 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
```typescript
// 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
```json
{
"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
```typescript
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
```typescript
// 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
```typescript
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
```sql
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
```typescript
@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
```typescript
// 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
- [webhook.site](https://webhook.site) - Receptor de prueba
- [ngrok](https://ngrok.com) - Tunel para localhost
---
## 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
- [Webhooks Best Practices](https://webhooks.dev/best-practices)
- [BullMQ Documentation](https://docs.bullmq.io/)
- [Stripe Webhooks](https://stripe.com/docs/webhooks) (referencia)
- [ADR-0007: Webhook Retry Strategy](../97-adr/ADR-0007-webhook-retry-strategy.md)
---
**Ultima actualizacion:** 2026-01-10
**Autor:** Backend Team