Channels · WhatsApp · Reference

Changelog

Notable changes to the WhatsApp business public REST API and this documentation site. Breaking changes are called out explicitly. For conventions and phased rollout, see the engineering contract in the backend repo (linked from the overview).

How we version

Paths are unversioned in v1 (no /v1). Additive endpoints and non-breaking response fields may ship without a path bump. Behavior that breaks existing clients is documented here before or with the release.

Webhooks Phase 5

Breaking: WhatsApp inbound requires envelope (Phase 5)

  • **Breaking:** Engagive no longer POSTs bare inbound message rows without `event`. Inbound is always `whatsapp.message.received` with the v1 envelope (`api_version`, `event_id`, `data.message`, `data.contact`, …).
  • `WHATSAPP_WEBHOOK_LEGACY_INBOUND` defaults to `false` and is not used for dual-write anymore.
  • Verify signatures with `X-Engagive-Signature` — see `/webhooks/signing`. BimaEngagive demo documents how to store `webhook_secret` and verify.
Webhooks Phase 4

WhatsApp phone/account typed webhooks (Phase 4)

  • New events: `whatsapp.phone.quality`, `whatsapp.phone.limit`, `whatsapp.account.updated` (included in `events: ["*"]`).
  • Meta sources: `phone_number_quality_update`, `business_capability_update`, `account_update`.
  • Prefer these typed envelopes over opt-in `whatsapp.meta.raw` for account/phone signals.
Webhooks Phase 3

WhatsApp webhook event subscriptions (Phase 3)

  • Webhook URLs accept `events` on create/update (default `["*"]` = all typed events).
  • `["*"]` does **not** include `whatsapp.meta.raw` — list it explicitly to opt in.
  • Dispatch only POSTs to URLs subscribed to that event (e.g. status-only URLs never get `received`).
Webhooks Phase 2

WhatsApp webhook signing + retries (Phase 2)

  • Create/rotate returns `webhook_secret` once; POSTs include `X-Engagive-Signature: t=<unix>,v1=<hmac_sha256>`.
  • Failed deliveries retry via BullMQ (default 4 attempts, exponential backoff). See `/webhooks/signing`.
  • `POST …/webhooks/:id/rotate-secret` rotates the signing secret.
Webhooks Phase 1

WhatsApp webhook envelope v1 (Phase 1)

  • Every typed POST uses a common envelope: `api_version`, `event`, `event_id`, `account_id`, `occurred_at`, `delivered_at`, `data`.
  • Primary events: `whatsapp.message.received`, `whatsapp.message.sent` (replaces `outgoing` as primary), `whatsapp.message.status`, `whatsapp.message.reaction`, `whatsapp.template.status`, `whatsapp.template.category`.
  • Inbound media is a single `received` after media is ready; separate `whatsapp.message.media` is off by default.
  • `whatsapp.meta.raw` is opt-in only; Meta `messages` changes no longer blast raw by default.
  • Soft dual-write: legacy bare inbound message row may still POST during migration (`WHATSAPP_WEBHOOK_LEGACY_INBOUND`). Prefer the envelope for new integrations.

Expanded WhatsApp business webhook events

  • Webhooks now also receive reactions, media-ready events, outbound-at-send, template status/category, account-scoped Meta raw changes, and statuses for incoming messages.
  • Typed payloads use an `event` field (e.g. `whatsapp.message.reaction`, `whatsapp.meta.raw`); inbound customer messages keep the message-row shape without `event`.

Webhook status updates for outbound messages

  • Registered WhatsApp webhook URLs now also receive Meta delivery statuses (`sent` / `delivered` / `read` / `failed`) for messages the business sent.
  • Status POSTs use `event: "whatsapp.message.status"` plus Engagive message fields; inbound customer messages keep the previous message-row shape.

Public responses no longer include metaResponse

  • Removed top-level `metaResponse` from public WhatsApp APIs (messages, template contents Meta sync).
  • Use Engagive `data` fields instead (`id`, `whatsapp_message_id`, `status`, `error_reason`, etc.).
Phase 5

Phase 5 — Postman docs and collection sync

  • Filled `/reference/postman` and `/reference/postman-environment` with import steps and downloads.
  • Postman collection expanded for Phases 1–4: accounts, contacts, media upload, history, webhooks, campaigns, Flows, template PUT.
  • Environment variables extended (`phone_id`, `contact_id`, `campaign_id`, `webhook_id`, `flow_id`).
  • Re-synced docs `examples/**/postman/` request bodies from the collection.
Phase 4

Phase 4 — Accounts, Flows, template update

  • Public accounts list/get (includes `phone_id` for campaigns).
  • Meta WhatsApp Flows: list/get/create/update/delete, preview, publish (not Automations).
  • Template metadata `PUT` by template code.
Phase 3

Phase 3 — Campaigns public API

  • Public campaigns under `…/campaigns`: list/get/create/update and create-with-audience-file.
  • Lifecycle: send-test, launch, schedule, pause, resume, cancel.
  • Reporting: recipients, analytics, events.
  • Portal `check-name` helper omitted from public surface.
Phase 2

Phase 2 — Contacts, history, media, inbound webhooks

  • Public contacts: list/get/create/update/delete, CSV sample, and multipart import.
  • Message history: `GET …/contacts/:contactId/messages` (page or `before` cursor).
  • Media upload: `POST …/messages/upload-media` (multipart `file`) for send-single `media_url`.
  • Inbound webhook config: list/create/PATCH/delete under `…/webhooks` (max 10 URLs).
  • Documented inbound POST payload Engagive sends to your callback URLs.
Phase 1

Phase 1 — Message sends persist to Engagive

  • Public send-single, send-template, and send-reaction now verify WhatsApp **account ownership** (cross-tenant → 404).
  • Sends **upsert a contact** from `to` (digits with country code) and **persist** outbound messages in chat history (same tables as the portal).
  • Success `data` now includes Engagive `id`, `whatsapp_message_id`, `contact_id`, `contact_created`, and `status`.
  • Meta failures after persist return **502** with `data.status: "failed"` and `error_reason` (message still visible in the portal inbox).
  • Templates and template-contents public handlers already enforced ownership; no route changes in this phase.
Phase 0

Phase 0 — Public API contract locked

  • Locked path convention: `/api/business/public/channels/whatsapp/...` (no `:channelId`).
  • Auth remains Bearer JWT (`api_access`) with portal RBAC; API keys deferred.
  • Identifiers: template/content **codes**; numeric ids for accounts, contacts, messages, campaigns, flows, webhooks.
  • Mandatory account ownership checks on all account-scoped public handlers.
  • Target surface defined: accounts (read), contacts, messages, templates, campaigns, Flows, inbound webhooks.
  • Explicit exclusions: admin, internal Meta ingestion, AI, Automations, Embedded Signup.
  • Engineering contract and build plan published under `engagive-app-backend/docs/public-api/`.

WhatsApp API overview