inbound-router¶
Lambda que recibe mensajes de canales (WhatsApp Meta/360, webchat) y los
normaliza para invocar al flow-engine. Dispatcher por eventSource.
Endpoints¶
API GW HTTP https://eqeloz3trh.execute-api.us-east-2.amazonaws.com:
| Path | Quién llama | Async/Sync |
|---|---|---|
POST /v1/webchat/messages |
Widget / clientes REST | sync (decisión ADR-0013) |
GET /v1/webchat/conversations/:id/messages |
Widget (long-poll) | sync |
POST /v1/webhooks/whatsapp-meta |
Meta Cloud | async (cola SQS) |
POST /v1/webhooks/whatsapp-360dialog |
360dialog | async |
POST /v1/webhooks/five9-digital |
Five9 | async |
Dispatcher por eventSource¶
El mismo handler atiende invocaciones desde API Gateway (HTTP),
EventBridge y SQS. El dispatcher en handler.ts decide:
if (event.requestContext?.http) {
// API GW HTTP → webchat sync o webhook
} else if (event.Records?.[0]?.eventSource === 'aws:sqs') {
// SQS → procesamiento batch async de webhooks
} else if (event.source === 'aws.events') {
// EventBridge scheduled
}
Strategies de inbound¶
| Strategy | Trigger | Verificación |
|---|---|---|
webchat-http |
POST /v1/webchat/messages |
tenant + widget + origen CORS + idempotency key |
whatsapp-meta-cloud |
POST webhook Meta | HMAC SHA-256 con app_secret (header x-hub-signature-256) |
whatsapp-360dialog |
POST webhook 360 | API key en header + HMAC opcional |
five9-digital |
POST webhook Five9 | basic auth + IP allowlist |
Las strategies viven en services/inbound-router/src/strategies/. Cada
una expone verify(event) → tenantId y normalize(event) → CanonicalMessage.
HMAC verification (ejemplo Meta)¶
import crypto from 'node:crypto';
export function verifyMetaSignature(rawBody: string, header: string, appSecret: string): boolean {
const [algo, sig] = header.split('=');
if (algo !== 'sha256') return false;
const expected = crypto.createHmac('sha256', appSecret).update(rawBody, 'utf8').digest('hex');
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
Secret en Secrets Manager /zen/dev/whatsapp-meta/<tenantId>/app-secret.
Dedup¶
Tabla DDB zen-dev-dedup con TTL 24h:
- PK:
TENANT#<id>#KEY#<idempotency-key>. - Atributos:
messageId,responseBody(para devolver el mismo response en hits cached),expiresAt.
CanonicalMessage¶
Estructura interna normalizada a partir de cualquier canal:
type CanonicalMessage = {
tenantId: string;
widgetId?: string; // sólo webchat
channel: 'webchat' | 'whatsapp' | 'whatsapp-360' | 'five9';
conversationId: string;
messageId: string;
text?: string;
attachments: Attachment[];
user: { id: string; firstName?: string; email?: string; phone?: string };
metadata: Record<string, unknown>;
receivedAt: string;
correlationId: string;
};
Errores comunes¶
| Error | Causa | Mitigación |
|---|---|---|
InvalidSignatureError |
HMAC no coincide | Verificar secret + raw body sin reparseo |
TenantNotFoundError |
tenant inexistente o suspendido | Devolver 404 al canal externo si conviene |
DedupConflict |
Misma idempotency key con body diferente | 409 |
RateLimitExceeded |
>30 msg/min/sesión | 429 con Retry-After |