Skip to content

Webhook entrante (forward)

No existe /platform/v1/inbound. Meta POSTea directamente al endpoint managed de Kalipto (/webhook/incoming); el router decide platform-vs-managed por el identifier de Meta y, si matchea un platform_channels activo, te reenvía el payload crudo a tu webhook_url.

¿Querés eventos tipados en lugar del payload Meta crudo? Kalipto también ofrece un modo normalizado (“kalipto”) con suscripciones a eventos tipados (message.received, conversation.ended, channel.connected, etc.), envelope estable, filtrado por tipo de evento, buffering, ordering per-conversación, auto-pause y observabilidad dual-mode. Ambos modos coexisten — el raw forward de esta página se configura per-canal vía PATCH /platform/dashboard/channels/{id}/webhook; el modo normalizado vive bajo /platform/v1/webhooks/*. Ver Webhooks normalizados.

Atributo del canalDescripción
webhook_urlTu URL HTTPS. Tiene que ser HTTPS. El SSRF guard rechaza IPs privadas (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), loopback (127.0.0.0/8, ::1), link-local (169.254.0.0/16), reservadas y la metadata (169.254.169.254). No seguimos redirects.
webhook_secretTu secret HMAC por canal. Lo guardamos cifrado con Fernet. El texto plano lo ves una sola vez al crearlo o regenerarlo. Es la clave para verificar X-Kalipto-Signature.
HeaderValor
Content-Typeapplication/json
User-AgentKalipto-Platform/1.0
X-Kalipto-Delivery-Id<platform_deliveries.id>: persistente en los reintentos (mismo id en cada intento).
X-Kalipto-Signaturesha256=<hmac_hex>: HMAC-SHA256 de los bytes UTF-8 exactos del body, con tu webhook_secret como clave. Omitido si el canal no tiene secret.

Body: el JSON crudo de Meta verbatim. Sin wrapping, sin envoltorio. Lo que Meta nos mandó es lo que recibís.

X-Hub-Signature-256 de Meta NO se reenvía. La verificación es contra X-Kalipto-Signature solamente: tu secret es la verdad.

Para que tu verificación HMAC produzca el mismo hex digest que firmamos, el body se serializa así (Python, idéntico a cómo lo firmamos):

raw_body = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")

Vos tenés que verificar contra los bytes exactos que recibís. Nunca después de re-serializar en tu stack.

Meta postea eventos no-mensaje al mismo endpoint: phone_number_quality_update, message_template_status_update, account_update. Los reenviamos también (el dispatcher no distingue tipos de evento, solo reenvía el payload crudo). Estos eventos NO consumen tu cuota de inbound.

Solo los mensajes “reales” de usuarios cuentan contra plan.inbound_limit. Los status callbacks (delivery, read, template-status) siempre se reenvían sin cobrar cupo. Cuando llegás al cap:

{"status":"platform","forwarded":false,"reason":"inbound_quota_exceeded"}

…le respondemos eso a Meta con 200 OK, NO creamos PlatformDelivery, y tu webhook NO se llama. Resolvelo bajándote a un plan superior.

El dispatcher persiste PlatformDelivery(status="pending") antes de cualquier POST. Si el proceso muere entre commit y dispatch, el worker levanta el row con un sweep cada 10s.

AttemptDelay antes de este intento
10 (inline tras commit)
21 minuto
35 minutos
430 minutos
5120 minutos
(dead-letter)360 minutos al dead-letter

Después de 5 intentos fallidos el delivery queda en failed. NO hay desactivación automática del canal en v1. Eso es responsabilidad del abuse monitor de plan upgrade.

State machine de platform_deliveries.status:

pending ──(worker/inline pickup)──▶ delivering ──(2xx)──▶ delivered
└──(non-2xx / timeout / SSRF)──▶ pending (con next_retry_at + attempts++)
└──(attempts >= 5)──▶ failed

next_retry_at en una row delivering actúa como lease de 2 minutos. Si tu worker muere mientras procesa, el sweep rescata el row después de que vence el lease.

  • Persist BEFORE POST: el row se commitea antes del HTTP. Cero pérdida por crash.
  • SSRF guard: HTTPS-only + IP blocklist + DNS-rebind TOCTOU cerrado con PinnedIPTransport (IP pinned al SSRF-time, TLS SNI + Host header preservados así tu cert valida).
  • Multi-worker safe: SELECT … FOR UPDATE SKIP LOCKED + Redis RPOP atómicos previenen doble-dispatch.