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íaPATCH /platform/dashboard/channels/{id}/webhook; el modo normalizado vive bajo/platform/v1/webhooks/*. Ver Webhooks normalizados.
Endpoint configuration
Section titled “Endpoint configuration”| Atributo del canal | Descripción |
|---|---|
webhook_url | Tu 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_secret | Tu 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. |
Shape del POST que recibís
Section titled “Shape del POST que recibís”| Header | Valor |
|---|---|
Content-Type | application/json |
User-Agent | Kalipto-Platform/1.0 |
X-Kalipto-Delivery-Id | <platform_deliveries.id>: persistente en los reintentos (mismo id en cada intento). |
X-Kalipto-Signature | sha256=<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-256de Meta NO se reenvía. La verificación es contraX-Kalipto-Signaturesolamente: tu secret es la verdad.
Serialización canónica del body
Section titled “Serialización canónica del body”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.
Status callbacks
Section titled “Status callbacks”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.
Cuota de inbound
Section titled “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.
Retry schedule + state machine
Section titled “Retry schedule + state machine”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.
| Attempt | Delay antes de este intento |
|---|---|
| 1 | 0 (inline tras commit) |
| 2 | 1 minuto |
| 3 | 5 minutos |
| 4 | 30 minutos |
| 5 | 120 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_aten una rowdeliveringactúa como lease de 2 minutos. Si tu worker muere mientras procesa, el sweep rescata el row después de que vence el lease.
Garantías
Section titled “Garantías”- 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 +Hostheader preservados así tu cert valida). - Multi-worker safe:
SELECT … FOR UPDATE SKIP LOCKED+ RedisRPOPatómicos previenen doble-dispatch.
Próximo paso
Section titled “Próximo paso”- Verificación HMAC: cómo verificar
X-Kalipto-Signatureen tu receiver.