waiaconnect

Documentación / Completo

Los 26 tipos de evento con su payload

Markdown

¿Programás con IA? Conectala a tu cuenta por MCP: revisa tu configuración, crea webhooks y plantillas con tu confirmación y te escribe el código. Ver cómo →

26 tipos suscribibles + webhook.test (no suscribible). Un endpoint con lista de events explícita recibe SOLO esos tipos; lista vacía/omitida = todos los de hoy. Un tipo nuevo que cambia lo que te llega (message.edited, message.revoked, onboarding.help_requested) no entra solo a un endpoint creado antes: volvé a mandar events: null (o elegilo en la lista) y queda sumado (newEventsAvailable dice cuáles faltan). El data de cada uno:

TipoSuscribibleDescripción
message.received✓Un cliente te escribió. data.message = objeto de mensaje de WhatsApp (texto/imagen/audio/…); data.contacts[] identifica al remitente. Un carrito del catálogo llega con type 'order' (ver «Carritos del catálogo»).
message.echo✓🌟 Diferencial de Coexistence: un mensaje que el DUEÑO envió DESDE EL TELÉFONO, con su contenido. origin='device' (no consume el plan). from = tu número de negocio.
message.status✓Acuse de un mensaje que enviaste por /v1. origin (api|device) distingue tu envío del envío hecho a mano desde el teléfono. Un acuse 'device' NO tiene mensaje tuyo detrás — es esperado. Desde el 29/09/2026 también llega cuando Meta rechaza NUESTRO envío después del 202: status 'failed', stage 'send' (campo opcional), failureCode (el mismo catálogo de lastErrorCode) y messageId = tu msg_… (Meta nunca le asignó un wamid). Una sola vez por mensaje.
connection.created✓Se conectó un número nuevo (panel u onboarding hospedado). Tu externalId viaja en el sobre → provisioná al cliente final en tu sistema.
onboarding.completed✓Tu cliente terminó el alta con un enlace (número nuevo o reconectado: data.reconnected). Va al webhook del número —el que trajo el enlace— o al de cuenta. Trae tu externalId y el wh_ del webhook del enlace.
onboarding.failed✓Un intento con tu enlace terminó sin número (falló, se canceló o se abandonó), con motivo (reason; coex_gate = Meta no habilitó la Convivencia para ese portafolio) y, si Meta los dio, su código y su session_id (metaSessionId, el que pide su soporte). Va a la URL del webhook del enlace, firmado con SU secreto; si el enlace no trajo webhook, al de cuenta (connection en null). El enlace sigue sirviendo mientras linkUsable sea true. Desde la 0.32.0, si el intento llegó sin código, lastKnownMetaErrorCode y lastKnownMetaErrorCodeAt traen el último código que Meta dio en un intento anterior del mismo enlace (y número); metaErrorCode no se pisa. Desde la 0.35.0, stage = meta_ok_connect_incomplete si Meta terminó su parte y Connect no sumó el número (por ejemplo, reason connection_limit_reached): arreglá la causa y mandá un enlace nuevo. Desde la 0.41.0, attemptId identifica el intento (el mismo de onboarding.help_requested); «Cancelar y volver a empezar» ya no emite este evento (no es un desenlace: el intento siguiente trae el real) y el GET del enlace dice reason restarted; y si el enlace no tiene código, lastKnownMetaErrorCode también mira tus otros enlaces con el mismo externalId de los últimos 7 días.
connection.status_changed✓Cambió el estado de una conexión (ej. status='disconnected'). status='banned' (nuevo): WhatsApp (Meta) bloqueó el número; no envía ni recibe, sigue ocupando su lugar en el plan y vuelve solo a 'connected' si Meta lo libera. Alertá o pausá los envíos a ese número.
message.edited✓El contacto editó un mensaje. Convivencia: textAvailable true y data.message = la VERSIÓN FINAL con la misma forma que message.received (texto, o imagen/video con el pie nuevo), con id = el wamid original. Sólo Cloud API: Meta no manda el texto → textAvailable false, message null, reason META_EDIT_TEXT_NOT_PROVIDED. originalMessageId = el wamid del mensaje editado (null si Meta no lo da). El texto anterior no lo guardamos: buscalo en tu base por originalMessageId.
message.revoked✓El contacto borró un mensaje (Convivencia; hasta 2 días después de mandarlo). originalMessageId = el wamid del mensaje borrado. Sin contenido.
connection.disconnected✓Un número dejó de estar conectado, una vez por baja: lo diste de baja vos (panel o DELETE /v1/connections/{id}, reason user_disconnected), Meta revocó el acceso (token_revoked) o el negocio quitó a WAIA Connect en Meta (partner_removed). source = api · panel · meta. Va a tus webhooks de CUENTA: los del número se apagan en ese momento.
connection.usage_threshold_reached✓El límite de UN número (su monthlyMessageLimit) llegó a 70/90/100%. Se emite UNA vez por (número, período, umbral). El comercial: avisale a ese cliente final o subile el tope.
usage.threshold_reached✓El cupo de la CUENTA (suma de todos los números) llegó a un umbral. Sólo existe con un plan a medida con cupo pactado: los planes públicos no tienen tope y no lo emiten. connection viene en null, sequence 0.
history.synced✓(Coexistence) resumen de cada tanda del historial que Meta sincroniza al conectar: cuántas conversaciones y cuántos mensajes, la fase y el avance (progress 100 = Meta terminó). shared:false = el dueño no compartió su historial en la app. Los mensajes llegan en history.batch sólo si se eligió recibirlos antes de conectar.
history.batch✓(Coexistence) los mensajes del historial —hasta 6 meses, las dos partes, sin grupos—, si el dueño eligió recibirlo antes de conectar. Hasta 500 por evento, en la forma de Meta (message), con contact y direction. Deduplicá por batchId: una tanda puede llegar dos veces. Va por una cola aparte que no demora lo en vivo. Con tu 2xx la borramos; si no, a los 7 días.
history.completed✓(Coexistence) ya no queda nada del historial de ese número por entregar: cuántos mensajes se entregaron (por webhook o por la API), cuántos vencieron y cuántos no se pudieron guardar. Llega una vez.
history.dropped✓(Coexistence) no pudimos guardar el historial de ese número para entregártelo (reason: storage_limit, o disabled). Lo ya guardado se entrega igual. El historial sigue en el teléfono. Llega una vez.
template.status_changed✓Cambió el estado de una plantilla en Meta (aprobada, rechazada, pausada, deshabilitada, calidad o categoría). Trae el estado nuevo y el motivo de rechazo traducido. Llega por una sincronización periódica: con unos minutos de demora.
template.draft_submitted✓Tu cliente final propuso una plantilla desde un enlace de propuesta. El borrador NO está en Meta: llega a Meta sólo cuando lo aprobás.
account.trial_warning✓La prueba gratis de la cuenta termina pronto (data.days = 7, 3 o 1). connection=null.
account.trial_expired✓Terminó la prueba gratis: se bloquea el envío (/v1 contesta 402 TRIAL_EXPIRED). Si nunca pagaste, la recepción se corta 7 días después, con aviso 2 días antes; si pagaste alguna vez, 30 días después, con aviso 7 días antes. data.receivingUntil = esa fecha (null sin un número conectado). connection=null.
subscription.activated✓(Facturación) el primer pago se confirmó y la cuenta del cliente del reseller pasó a activa. connection=null. Reaccioná activando su servicio.
subscription.cancelled✓(Facturación) baja de la suscripción. Sigue funcionando hasta accessUntil; después se bloquea el envío, y la recepción se corta 30 días después del bloqueo (con aviso 7 días antes). connection=null.
billing.payment_failed✓(Facturación) un cobro recurrente falló. Entra en aviso/gracia (no corta nada todavía). Duneá a tu cliente ANTES del corte. connection=null.
billing.payment_recovered✓(Facturación) el pago se regularizó y la cuenta se reactivó sola. connection=null.
billing.amount_updated✓(Facturación) cambió el importe de tu próxima renovación. Podés mostrarle el importe a tu cliente antes de que se cobre. connection=null. ⚠ Con tarifa plana el cambio es del PLAN o de la cotización: overageArs y overageMessages llegan en 0 (sólo un plan a medida con cupo pactado los mueve).
webhook.test—El evento del botón 'Probar' del panel. NO es suscribible: se entrega SIEMPRE a ese endpoint (aunque tenga lista de eventos explícita) para validar conectividad + firma. connection=null, data.test=true. Nunca lo trates como tráfico real.
onboarding.help_requested✓Tu cliente se trabó en el alta por enlace y escribió qué vio. clientText = su texto TEXTUAL; metaErrorCode y metaSessionId (también sacados del texto si pegó «#código:session_id»); responsible y diagnosis (qué es y qué hacer, en el idioma del enlace; null si no hay con qué; source = catalog, o ai con su confidence sólo si el código es nuevo o no hay código); caseId (A-<n>) y hasScreenshot. Catálogo 3: se pide por nombre.
message.received — data
{
  "message": {
    "id": "wamid.HBgL…",
    "from": "5493511234567",
    "type": "text",
    "text": {
      "body": "hola"
    }
  },
  "contacts": [
    {
      "wa_id": "5493511234567",
      "profile": {
        "name": "Ana"
      }
    }
  ]
}
message.echo — data
{
  "origin": "device",
  "message": {
    "id": "wamid.HBgL…",
    "from": "5493516516690",
    "to": "5493511234567",
    "type": "text",
    "timestamp": "1754531482",
    "text": {
      "body": "te confirmo el turno para mañana 10hs"
    }
  }
}
message.status — data
{
  "messageId": "wamid.HBgL…",
  "status": "read",
  "origin": "device",
  "recipient": "5493511234567",
  "timestamp": "2026-08-06T00:31:25.000Z"
}
connection.created — data
{
  "status": "connected"
}
onboarding.completed — data
{
  "onboardingLinkId": "onb_ca18…",
  "externalId": "pharmacy-42",
  "label": "Pharmacy 42",
  "reconnected": false,
  "mode": "coexistence",
  "displayName": "Pharmacy 42",
  "historyDelivery": false,
  "webhookId": "wh_5c1e…"
}
onboarding.failed — data
{
  "onboardingLinkId": "onb_ca18…",
  "externalId": "pharmacy-42",
  "label": "Pharmacy 42",
  "attemptId": "sa_5f1e2d3c4b5a",
  "attemptStatus": "failed",
  "reason": "meta_error",
  "metaErrorCode": 2655093,
  "metaMessage": "The business is already sharing this WhatsApp Business account with a partner…",
  "metaSessionId": "01a1b2c3-…",
  "lastKnownMetaErrorCode": null,
  "lastKnownMetaErrorCodeAt": null,
  "stage": null,
  "failedAttempts": 1,
  "linkUsable": true,
  "linkExpiresAt": "2026-10-09T18:00:00.000Z"
}
connection.status_changed — data
{
  "status": "disconnected",
  "previousStatus": "connected"
}
message.edited — data
{
  "originalMessageId": "wamid.HBgNNTQ5MTE1NTU1MDEwMBUCABIYFjNFQjBBMUMyRDNFNEY1QTZCN0M4RDkA",
  "originalConnectMessageId": null,
  "editMessageId": "wamid.HBgNNTQ5MTE1NTU1MDEwMBUCABIYFjNFQjBFRElUMDAwMDAwMDAwMDAwMDEA",
  "editedAt": "2026-10-23T14:05:09.000Z",
  "from": "5491155550100",
  "connectionId": "conn_4eede070e5a84d1590bdce2ea1d837bc",
  "contacts": [
    {
      "profile": {
        "name": "Ana"
      },
      "wa_id": "5491155550100"
    }
  ],
  "textAvailable": true,
  "message": {
    "id": "wamid.HBgNNTQ5MTE1NTU1MDEwMBUCABIYFjNFQjBBMUMyRDNFNEY1QTZCN0M4RDkA",
    "from": "5491155550100",
    "timestamp": "1792764309",
    "type": "text",
    "text": {
      "body": "Te transfiero $ 15.400 (no 14.500)"
    }
  },
  "reason": null
}
message.revoked — data
{
  "originalMessageId": "wamid.HBgNNTQ5MTE1NTU1MDEwMBUCABIYFjNFQjBBMUMyRDNFNEY1QTZCN0M4RDkA",
  "originalConnectMessageId": null,
  "revokeMessageId": "wamid.HBgNNTQ5MTE1NTU1MDEwMBUCABIYFjNFQjBSRVZPS0UwMDAwMDAwMDAwMDEA",
  "revokedAt": "2026-10-23T14:20:41.000Z",
  "from": "5491155550100",
  "connectionId": "conn_4eede070e5a84d1590bdce2ea1d837bc",
  "contacts": [
    {
      "profile": {
        "name": "Ana"
      },
      "wa_id": "5491155550100"
    }
  ]
}
connection.disconnected — data
{
  "status": "disconnected",
  "previousStatus": "connected",
  "reason": "user_disconnected",
  "source": "api",
  "disconnectedAt": "2026-10-11T18:04:12.000Z"
}
connection.usage_threshold_reached — data
{
  "scope": "connection",
  "threshold": 90,
  "used": 900,
  "limit": 1000,
  "period": "2026-08",
  "connection": {
    "id": "conn_4eede070e5a84d1590bdce2ea1d837bc",
    "label": "Farmacia López",
    "externalId": "farmacia-lopez"
  }
}
usage.threshold_reached — data
{
  "scope": "account",
  "threshold": 90,
  "used": 22610,
  "limit": 25000,
  "period": "2026-08"
}
history.synced — data
{
  "conversations": 34,
  "messages": 512,
  "shared": true,
  "phase": 0,
  "chunkOrder": 1,
  "progress": 55
}
history.batch — data
{
  "batchId": "hb_3c1f0a9b8e7d4c2b9a1f0e3d2c1b0a9f",
  "seq": 1,
  "phase": 1,
  "chunkOrder": 3,
  "progress": 40,
  "count": 2,
  "expiresAt": "2026-10-11T03:00:00.000Z",
  "messages": [
    {
      "contact": "5493511234567",
      "direction": "in",
      "message": {
        "id": "wamid.HBgL…",
        "from": "5493511234567",
        "timestamp": "1739230970",
        "type": "text",
        "text": {
          "body": "¿Tienen turno el lunes?"
        },
        "history_context": {
          "status": "READ"
        }
      }
    },
    {
      "contact": "5493511234567",
      "direction": "out",
      "message": {
        "id": "wamid.HBgM…",
        "from": "5493510000000",
        "timestamp": "1739231001",
        "type": "text",
        "text": {
          "body": "Sí, a las 10."
        },
        "history_context": {
          "status": "DELIVERED"
        }
      }
    }
  ]
}
history.completed — data
{
  "batches": 12,
  "delivered": 5480,
  "deliveredByWebhook": 5480,
  "deliveredByApi": 0,
  "expired": 0,
  "dropped": 0
}
history.dropped — data
{
  "reason": "storage_limit",
  "messages": 500
}
template.status_changed — data
{
  "template": {
    "name": "turno_confirmado",
    "language": "es_AR"
  },
  "event": "message_template_status_update",
  "status": "APPROVED",
  "category": "UTILITY",
  "qualityScore": null,
  "reason": null,
  "rejection": null
}
template.draft_submitted — data
{
  "draft": {
    "id": "tpl_…",
    "name": "promo_octubre",
    "language": "es_AR",
    "suggestedCategory": "MARKETING"
  },
  "proposedVia": {
    "linkId": "onb_…",
    "label": "Farmacia Centro"
  },
  "clientNotes": null,
  "precheck": {
    "warnings": []
  }
}
account.trial_warning — data
{
  "days": 3,
  "trialEndsAt": "2026-09-13T00:00:00.000Z"
}
account.trial_expired — data
{
  "trialEndsAt": "2026-09-13T00:00:00.000Z",
  "receivingUntil": "2026-09-20T00:00:00.000Z"
}
subscription.activated — data
{
  "planCode": "growth",
  "periodEnd": "2026-09-13T00:00:00.000Z"
}
subscription.cancelled — data
{
  "accessUntil": "2026-09-13T00:00:00.000Z"
}
billing.payment_failed — data
{
  "paymentId": "123456789",
  "amount": 59000,
  "currency": "ARS"
}
billing.payment_recovered — data
{
  "planCode": "growth",
  "paymentId": "123456789"
}
billing.amount_updated — data
{
  "totalArs": 52500,
  "baseArs": 52500,
  "overageArs": 0,
  "overageMessages": 0
}
webhook.test — data
{
  "type": "test",
  "test": true,
  "message": "WAIA Connect test event — your endpoint is reachable and verifying signatures. This is NOT real traffic."
}
onboarding.help_requested — data
{
  "onboardingLinkId": "olk_8f2c1d3e4b5a6978",
  "externalId": "cliente-1234",
  "label": "Panadería Ejemplo",
  "attemptId": "sa_5f1e2d3c4b5a",
  "attemptStatus": "failed",
  "reason": "incomplete",
  "mode": "coexistence",
  "locale": "es",
  "metaErrorCode": 3441060,
  "metaSessionId": "0f3c2a1e-5b7d-4c9a-8e21-000000000000",
  "metaErrorFrom": "client_text",
  "responsible": "client",
  "diagnosis": {
    "key": "3441060",
    "metaErrorCode": 3441060,
    "title": "Meta Business AI activa en el número",
    "cause": "La IA de Meta está encendida en la app WhatsApp Business de ese teléfono y Meta no deja usar la Convivencia así.",
    "steps": [
      "Que tu cliente abra WhatsApp Business → Herramientas para la empresa → Meta Business AI y la desactive.",
      "Que vuelva a abrir el mismo enlace y conecte."
    ],
    "links": [],
    "responsible": "client",
    "source": "catalog",
    "catalogVersion": 1,
    "locale": "es"
  },
  "clientText": "Me sale esto: Meta Business AI está activado en este número. #3441060:0f3c2a1e-5b7d-4c9a-8e21-000000000000",
  "noteId": 12,
  "caseId": "A-12",
  "hasScreenshot": false,
  "linkUsable": true,
  "linkExpiresAt": "2026-10-27T09:14:00.000Z"
}