waiaconnect

Documentation / Complete

The 26 event types with their payload

Markdown

Building with AI? Connect it to your account over MCP: it checks your setup, creates webhooks and templates with your confirmation and writes the code for you. See how →

26 subscribable types + webhook.test (not subscribable). An endpoint with an explicit events list receives ONLY those types; empty/omitted = all of today's. A new type that changes what you get (message.edited, message.revoked, onboarding.help_requested) does not reach an endpoint created before it on its own: send events: null again (or pick it in the list) and it is added (newEventsAvailable says which are missing). The data of each:

TypeSubscribableDescription
message.received✓A customer messaged you. data.message = WhatsApp message object (text/image/audio/…); data.contacts[] identifies the sender. A catalog cart arrives with type 'order' (see «Catalog shopping carts»).
message.echo✓🌟 The Coexistence differentiator: a message the OWNER sent FROM THE PHONE, with its content. origin='device' (does not consume the plan). from = your business number.
message.status✓Receipt for a message you sent via /v1. origin (api|device) tells your send apart from one made by hand on the phone. A 'device' receipt has NO message of yours behind it — that's expected. Since 2026-09-29 it also arrives when Meta rejects OUR send after the 202: status 'failed', stage 'send' (optional field), failureCode (the lastErrorCode catalog) and messageId = your msg_… (Meta never assigned a wamid). Once per message.
connection.created✓A new number connected (panel or hosted onboarding). Your externalId travels in the envelope → provision the end-customer in your system.
onboarding.completed✓Your customer finished onboarding with a link (new or reconnected number: data.reconnected). Goes to the number's webhook —the one the link brought— or the account one. Carries your externalId and the link webhook's wh_.
onboarding.failed✓An attempt with your link ended without a number (failed, cancelled or abandoned), with a reason (coex_gate = Meta did not enable Coexistence for that portfolio) and, if Meta gave them, its code and its session_id (metaSessionId, the one its support asks for). Goes to the link webhook's URL, signed with ITS secret; if the link had no webhook, to the account one (connection null). The link still works while linkUsable is true. Since 0.32.0, if the attempt came without a code, lastKnownMetaErrorCode and lastKnownMetaErrorCodeAt carry the last code Meta gave on an earlier attempt of the same link (and number); metaErrorCode is never overwritten. Since 0.35.0, stage = meta_ok_connect_incomplete when Meta finished its part and Connect did not add the number (for example, reason connection_limit_reached): fix the cause and send a new link. Since 0.41.0, attemptId identifies the attempt (the same one as onboarding.help_requested); «Cancel and start over» no longer emits this event (it is not an outcome: the next attempt brings the real one) and the link's GET says reason restarted; and when the link has no code, lastKnownMetaErrorCode also looks at your other links with the same externalId from the last 7 days.
connection.status_changed✓A connection's status changed (e.g. status='disconnected'). status='banned' (new): WhatsApp (Meta) blocked the number; it cannot send or receive, keeps its slot in your plan and goes back to 'connected' by itself if Meta lifts the block. Alert or pause sends to that number.
message.edited✓The contact edited a message. Coexistence: textAvailable true and data.message = the FINAL VERSION with the same shape as message.received (text, or image/video with the new caption), with id = the original wamid. Cloud API only: Meta does not send the text → textAvailable false, message null, reason META_EDIT_TEXT_NOT_PROVIDED. originalMessageId = the wamid of the edited message (null if Meta does not give it). We don't store the previous text: look it up in your own database by originalMessageId.
message.revoked✓The contact deleted a message (Coexistence; up to 2 days after sending it). originalMessageId = the wamid of the deleted message. No content.
connection.disconnected✓A number stopped being connected, once per disconnection: you disconnected it (dashboard or DELETE /v1/connections/{id}, reason user_disconnected), Meta revoked the access (token_revoked) or the business removed WAIA Connect in Meta (partner_removed). source = api · panel · meta. Goes to your ACCOUNT webhooks: the number's own are switched off at that moment.
connection.usage_threshold_reached✓ONE number's limit (its monthlyMessageLimit) hit 70/90/100%. Emitted ONCE per (number, period, threshold). The commercial one: warn that end-customer or raise the cap.
usage.threshold_reached✓The ACCOUNT quota (sum of all numbers) hit a threshold. Only exists on a custom plan with an agreed quota: public plans have no cap and never emit it. connection is null, sequence 0.
history.synced✓(Coexistence) summary of each history chunk Meta syncs at connect: how many conversations and messages, the phase and the progress (progress 100 = Meta finished). shared:false = the owner did not share their history in the app. The messages arrive as history.batch only if receiving them was chosen before connecting.
history.batch✓(Coexistence) the history messages —up to 6 months, both sides, no groups—, if the owner chose to receive it before connecting. Up to 500 per event, in Meta's shape (message), with contact and direction. Deduplicate on batchId: a batch can arrive twice. Sent on a separate queue that never delays live events. Your 2xx deletes it; otherwise it is deleted after 7 days.
history.completed✓(Coexistence) nothing of that number's history is left to deliver: how many messages were delivered (by webhook or by API), how many expired and how many could not be stored. Sent once.
history.dropped✓(Coexistence) we could not store that number's history for delivery (reason: storage_limit, or disabled). What was already stored is still delivered. The history is still on the phone. Sent once.
template.status_changed✓A template's state changed at Meta (approved, rejected, paused, disabled, quality or category). Carries the new status and the translated rejection reason. Delivered by a periodic sync: a few minutes late.
template.draft_submitted✓Your end customer proposed a template through a proposal link. The draft is NOT at Meta: it reaches Meta only when you approve it.
account.trial_warning✓The account's free trial ends soon (data.days = 7, 3 or 1). connection=null.
account.trial_expired✓The free trial ended: sending is blocked (/v1 answers 402 TRIAL_EXPIRED). If you never paid, receiving is cut 7 days later, with a notice 2 days before; if you ever paid, 30 days later, with a notice 7 days before. data.receivingUntil = that date (null without a connected number). connection=null.
subscription.activated✓(Billing) the first payment was confirmed and the reseller's client account became active. connection=null. React by enabling their service.
subscription.cancelled✓(Billing) subscription cancelled. Works until accessUntil; then sending is blocked, and receiving is cut 30 days after the block (with a notice 7 days before). connection=null.
billing.payment_failed✓(Billing) a recurring charge failed. Enters warning/grace (nothing is cut yet). Dun your client BEFORE the cutoff. connection=null.
billing.payment_recovered✓(Billing) the payment was recovered and the account reactivated on its own. connection=null.
billing.amount_updated✓(Billing) the amount of your next renewal changed. You can show your client the amount before it is charged. connection=null. ⚠ Under flat rate the change is the PLAN or the FX rate: overageArs and overageMessages come back as 0 (only a bespoke plan with an agreed allowance moves them).
webhook.test—The panel 'Test' button event. NOT subscribable: it is ALWAYS delivered to that endpoint (even with an explicit event list) to validate connectivity + signature. connection=null, data.test=true. Never treat it as real traffic.
onboarding.help_requested✓Your customer got stuck in the link signup and wrote what they saw. clientText = their text VERBATIM; metaErrorCode and metaSessionId (also taken from the text if they pasted «#code:session_id»); responsible and diagnosis (what it is and what to do, in the link's language; null when nothing is known; source = catalog, or ai with its confidence only when the code is new or there is no code); caseId (A-<n>) and hasScreenshot. Catalog 3: list it by name.
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"
}