Documentation / Complete
The 26 event types with their payload
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:
| Type | Subscribable | Description |
|---|---|---|
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"
}