diff --git a/gateway/platforms/whatsapp_cloud.py b/gateway/platforms/whatsapp_cloud.py index 47631f69c1..e8ba02a663 100644 --- a/gateway/platforms/whatsapp_cloud.py +++ b/gateway/platforms/whatsapp_cloud.py @@ -3,7 +3,8 @@ aiohttp webhook). Complements the Baileys bridge plugin; both share gating / men formatting behavior via ``WhatsAppBehaviorMixin``. Required env: WHATSAPP_CLOUD_PHONE_NUMBER_ID, WHATSAPP_CLOUD_ACCESS_TOKEN. Optional: -WHATSAPP_CLOUD_APP_ID, _APP_SECRET (HMAC key for X-Hub-Signature-256), _WABA_ID, +WHATSAPP_CLOUD_APP_ID, _APP_SECRET (HMAC key for X-Hub-Signature-256), +_WABA_ID (when set, inbound webhooks must match it), _VERIFY_TOKEN (hub.verify_token), _WEBHOOK_HOST (unset → dual-stack all interfaces), _WEBHOOK_PORT (8090), _WEBHOOK_PATH (/whatsapp/webhook), _API_VERSION (v20.0).""" @@ -253,7 +254,7 @@ class WhatsAppCloudAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter): incoming_phone_number_id = str(metadata.get("phone_number_id") or "").strip() incoming_waba_id = str(entry_id or "").strip() return ( - (not self._phone_number_id or incoming_phone_number_id == self._phone_number_id) + incoming_phone_number_id == self._phone_number_id and (not self._waba_id or incoming_waba_id == self._waba_id) ) @@ -775,12 +776,20 @@ class WhatsAppCloudAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter): if not isinstance(change, dict) or change.get("field") != "messages": continue # account_alerts, template_status_update, … — not message ingress value = change.get("value") or {} - if not isinstance(value, dict) or not self._webhook_identity_matches( - entry.get("id"), value.get("metadata") - ): - logger.warning( - "[whatsapp_cloud] ignoring signed webhook whose WABA or phone-number identity " - "does not match this adapter" + if not isinstance(value, dict): + continue + metadata = value.get("metadata") or {} + if not self._webhook_identity_matches(entry.get("id"), metadata): + # A shared Meta app delivers sibling numbers' status receipts here too; + # only a foreign *message* is worth an operator-visible warning. + logger.log( + logging.WARNING if value.get("messages") else logging.DEBUG, + "[whatsapp_cloud] ignoring signed webhook for waba=%r phone_number_id=%r " + "(configured waba=%s phone_number_id=%r)", + entry.get("id"), + metadata.get("phone_number_id") if isinstance(metadata, dict) else None, + self._waba_id or "", + self._phone_number_id, ) continue contacts_by_waid = { @@ -790,7 +799,7 @@ class WhatsAppCloudAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter): } for raw_message in value.get("messages") or []: if isinstance(raw_message, dict): - await self._ingest_message(raw_message, contacts_by_waid, value.get("metadata") or {}) + await self._ingest_message(raw_message, contacts_by_waid, metadata) for status in value.get("statuses") or []: if isinstance(status, dict): logger.debug("[whatsapp_cloud] status %s for %s", status.get("status"), status.get("id")) diff --git a/hermes_cli/setup_whatsapp_cloud.py b/hermes_cli/setup_whatsapp_cloud.py index b0308a69fa..bf752de045 100644 --- a/hermes_cli/setup_whatsapp_cloud.py +++ b/hermes_cli/setup_whatsapp_cloud.py @@ -230,7 +230,7 @@ def _credential_step(step) -> bool: def _step_optional_ids() -> dict: """STEP 4: optional App ID / WABA ID. Returns {env var: effective value or None}.""" from hermes_cli.config import get_env_value - _header("STEP 4 — App ID & WABA ID (optional, for analytics)") + _header("STEP 4 — App ID & WABA ID (optional; WABA ID binds inbound webhooks)") ids = {} for label, env_var, validator, help_text in _OPTIONAL_ID_STEPS: current = get_env_value(env_var) or None