fix(whatsapp): tighten cloud webhook identity binding and its diagnostics

The identity guard skipped non-dict change values with a warning that
claimed an identity mismatch, and the mismatch warning gave operators no
way to tell which number/WABA was arriving versus configured. A shared
Meta app also delivers status receipts for sibling numbers, so warning
on every one is noise: warn only when the foreign change carries
messages, debug otherwise. Malformed values are now skipped silently.

The phone_number_id arm's "not configured" escape was dead (the adapter
cannot start without a phone number id), so compare it directly.

The setup wizard STEP 4 header and module docstring still described the
WABA ID as analytics-only; it now binds inbound webhooks.
This commit is contained in:
kshitijk4poor
2026-10-01 21:55:48 +05:30
committed by kshitij
parent 4152c69636
commit e5e2d5508f
2 changed files with 19 additions and 10 deletions

View File

@@ -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 "<any>",
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"))

View File

@@ -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