Skip to main content

Module whatsapp

Module whatsapp 

Source
Expand description

WhatsApp Cloud API messaging bridge — inbound webhook receiver + outbound Graph API send. See docs/specs/SPEC_MESSAGING_INTEGRATION_WHATSAPP_2026_07_07.md.

Lifecycle:

  1. WhatsAppBridge::init_global(config, http) — call once at startup.
  2. The outbound send loop runs in a background tokio task, reading from an mpsc channel — same shape as Discord/Telegram/Slack, so this bridge implements the real (sync) MessagingBridge::send exactly like its siblings, rather than the async-trait alternative the spec sketched before the trait had actually shipped (see §5.1’s “open decision point” — resolved here in favor of matching the merged Discord/Telegram/Slack convention instead of relitigating it).
  3. Inbound is passive: Meta POSTs to GET/POST /webhook/whatsapp, handled by ordinary axum request tasks (webhook.rs), not a loop this module drives. That’s already fully concurrent with the outbound send loop — one lives on axum’s request-handling tasks, the other on its own tokio::spawned task — so there’s no interleaving hazard to split apart the way telegram/poller.rs and slack/socket.rs had to (see those files’ doc comments for the review finding that motivated their inbound/outbound task split).
  4. WhatsAppBridge::get() returns the singleton for HTTP handler / webhook handler use.
  5. bridge.send(msg) enqueues a message for the Graph API sender.
  6. bridge.health() returns the current status.

§Scope deviation from the spec: no automated tunnel management

The spec (§4) designs a TunnelManager that spawns and supervises a cloudflared subprocess, auto-detects its connected URL, and sequences bridge startup around it. That subsystem cannot be meaningfully built or verified in this environment (no live Cloudflare/Meta credentials, no way to exercise a real tunnel handshake), so it is not implemented in this PR. v1 assumes the operator has already stood up their own tunnel (Cloudflare Tunnel, ngrok, or otherwise) pointed at this instance’s webhook port, and has manually registered the callback URL + verify token in Meta’s App Dashboard — both one-time, out-of-band steps independent of agentmux-srv’s process lifecycle. messaging:whatsapp:tunnel_domain is kept as a config field purely so the startup log can print the full callback URL as a convenience reminder; nothing reads it to manage a subprocess. messaging::tunnel (the spec’s shared TunnelManager) is not created. This scoping decision means BridgeHealth reflects only “the bridge is initialized and the outbound sender is running”, not “Meta can currently reach us” — the latter depends on infrastructure this process doesn’t own or observe.

Re-exports§

pub use webhook::handle_inbound;
pub use webhook::handle_verify;

Modules§

rest
WhatsApp Cloud API REST client — outbound message send only.
types
WhatsApp Cloud API wire types — webhook payload (inbound) and Graph API send body (outbound). Hand-rolled serde structs over reqwest + serde_json, matching Discord’s types.rs precedent (no SDK crate).
webhook 🔒
WhatsApp Cloud API inbound webhook receiver — axum handlers for the one-time verification handshake and the per-event delivery POST.

Structs§

WhatsAppBridge
WhatsAppConfig

Statics§

GLOBAL_BRIDGE 🔒

Functions§

run_outbound_loop 🔒
Drains the outbound mpsc queue and calls the Graph API for each message. Runs as its own tokio::spawned task, fully independent of the axum request tasks servicing webhook::handle_inbound — a slow or rate-limited send here cannot block the webhook route from accepting Meta’s next delivery (see module doc comment).