docs/13-webhooks.md

13 - Webhooks

Eventos

  • otp.sent
  • otp.delivery_failed
  • otp.fallback_sent
  • otp.verified
  • otp.expired
  • otp.blocked
  • otp.cancelled
  • license.over_quota
  • provider.outage_detected

Payload base

{
  "id": "wh_evt_123",
  "type": "otp.fallback_sent",
  "clientId": "client_001",
  "applicationId": "app_001",
  "licenseId": "lic_001",
  "operationId": "otp_op_123",
  "createdAt": "2026-07-03T18:30:00Z",
  "data": {
    "fromChannel": "whatsapp",
    "toChannel": "sms",
    "reason": "WHATSAPP_PROVIDER_UNAVAILABLE",
    "billable": true
  }
}

Firma

X-Kodenix-Timestamp: 1783123456
X-Kodenix-Signature: sha256=...

Entrega asíncrona OTP

{
  "id": "wh_evt_456",
  "type": "otp.delivery_failed",
  "operationId": "op_123",
  "createdAt": "2026-07-25T20:00:00Z",
  "data": {
    "challengeId": "challenge_456",
    "status": "DELIVERY_FAILED",
    "channel": "whatsapp",
    "error": {
      "code": "OTP_PHONE_NOT_REGISTERED",
      "message": "El teléfono no está registrado en WhatsApp",
      "recoverable": true,
      "action": "CHOOSE_ANOTHER_CHANNEL"
    },
    "occurredAt": "2026-07-25T20:00:00Z"
  }
}

El receptor deduplica por X-Kodenix-Event-Id/id, verifica timestamp y firma sobre el body crudo antes de procesar, y responde 2xx solo cuando acepta el evento. Los reintentos pueden duplicar eventos. El consumidor debe tolerar llegada fuera de orden usando occurredAt y no retroceder un estado terminal por un evento más antiguo.

La política exacta de backoff, ventana máxima de reintentos y garantía formal de orden aún requiere definición backend. Hasta fijarla, no se promete entrega exactamente una vez ni orden global. La aplicación móvil no consulta endpoints privados; el backend integrador recibe estos cambios.