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:
WhatsAppBridge::init_global(config, http)— call once at startup.- 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::sendexactly 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). - 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 owntokio::spawned task — so there’s no interleaving hazard to split apart the waytelegram/poller.rsandslack/socket.rshad to (see those files’ doc comments for the review finding that motivated their inbound/outbound task split). WhatsAppBridge::get()returns the singleton for HTTP handler / webhook handler use.bridge.send(msg)enqueues a message for the Graph API sender.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’stypes.rsprecedent (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§
Statics§
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 servicingwebhook::handle_inbound— a slow or rate-limited send here cannot block the webhook route from accepting Meta’s next delivery (see module doc comment).