Spaces:
Running
WhatsApp Service Gateway
The main FastAPI application (port 7860) is the public API gateway. It
proxies every request under /api/whatsapp/* to the internal WhatsApp service
(a Go/whatsmeow application), which is not exposed publicly.
Client
| HTTP
v
Main FastAPI :7860
| /api/whatsapp/...
v
WhatsApp Service (internal, e.g. http://localhost:8231)
|
v
Client
Route mapping
The gateway forwards the full path, query string, HTTP method and body
unchanged. All routes require the main application's API key
(Authorization: Bearer <API_KEY>).
| Client request | WhatsApp service call |
|---|---|
GET /api/whatsapp/server/ok |
GET /server/ok (health) |
POST /api/whatsapp/instance/create |
POST /instance/create (admin) |
GET /api/whatsapp/instance/status |
GET /instance/status |
POST /api/whatsapp/send/text |
POST /send/text |
DELETE /api/whatsapp/instance/delete/:id |
DELETE /instance/delete/:id |
No path transformation is applied: after the /api/whatsapp prefix is stripped,
the rest of the path is appended to WHATSAPP_SERVICE_URL.
Deployment model
Single Docker container (Hugging Face Spaces style): the Go binary is compiled
in a multi-stage whatsapp-build stage and embedded in the image at
/app/whatsapp-service/server. start.sh launches it as a sibling process and
uvicorn is the primary process. The gateway reaches it over loopback
(http://127.0.0.1:8231).
A crash of the WhatsApp service does not take the main app down: the gateway
returns sanitized 502/503 responses until it recovers (restart loop every
5s, degraded mode).
Gateway behaviour
- Auth: the gateway requires the main app's Bearer API key. The WhatsApp
service's own
apikeyheader is forwarded as-is when provided by the client (per-instance token); otherwise the configuredWHATSAPP_SERVICE_GLOBAL_API_KEYis injected (admin routes). - Headers: hop-by-hop headers and the gateway's
Authorization/Cookieare never forwarded. Response headers are whitelisted — internalServer/content-encoding/hop-by-hop headers never reach the client. - Errors: connection failures →
502; timeouts →504; disabled service →503. Upstream 4xx/5xx responses are passed through unchanged. Error bodies never contain internal hostnames, URLs, exceptions or stack traces. - Payments/streaming: responses are streamed back to the client (media/QR downloads are not buffered).
Configuration
| Variable | Default | Description |
|---|---|---|
WHATSAPP_SERVICE_ENABLED |
false |
Enable the gateway routes. start.sh auto-enables it when the binary exists. |
WHATSAPP_SERVICE_URL |
http://localhost:8231 |
Base URL of the internal WhatsApp service. |
WHATSAPP_SERVICE_TIMEOUT |
30 |
Read/write/pool timeout for upstream calls (seconds). |
WHATSAPP_SERVICE_CONNECT_TIMEOUT |
5 |
Connect timeout for upstream calls (seconds). |
WHATSAPP_SERVICE_GLOBAL_API_KEY |
(empty) | The WhatsApp service GLOBAL_API_KEY, injected as the default apikey header. |
WHATSAPP_SERVICE_PORT |
8231 |
Port start.sh uses to launch the embedded binary (SERVER_PORT). |
WHATSAPP_SERVICE_BINARY |
/app/whatsapp-service/server |
Path to the embedded binary. |
WhatsApp service env vars (centralized in the main app)
There is exactly one configuration source: the main application's .env
(or the deployment platform's environment). The Go service never reads a
.env of its own — start.sh / the Docker image inject its variables as
process environment, and --dev only optionally loads a local .env if one
exists. Full reference: the WhatsApp section of .env.example.
Values shared with the main app use the same names (SUPABASE_URL,
REDIS_URL, ...). A few names are mapped automatically by start.sh so you
only configure one value:
| Centralized setting | Injected into Go service as |
|---|---|
SUPABASE_SERVICE_ROLE_KEY |
SUPABASE_SERVICE_KEY |
WHATSAPP_SERVICE_GLOBAL_API_KEY |
GLOBAL_API_KEY |
WHATSAPP_SERVICE_PORT |
SERVER_PORT |
Other Go-service settings configured centrally: SUPABASE_DB_URL,
DATABASE_SAVE_MESSAGES, CLIENT_NAME, CONNECT_ON_STARTUP, DEBUG_ENABLED,
LOG_TYPE, WEBHOOK_FILES, OS_NAME, WHATSAPP_VERSION_*, MINIO_*,
AMQP_*, NATS_*, WEBHOOK_URL, PROXY_*, API_AUDIO_CONVERTER*,
EVENT_IGNORE_*, QRCODE_MAX_COUNT, CHECK_USER_EXISTS, LOG_*.
Configure
WHATSAPP_SERVICE_GLOBAL_API_KEY(equivalentlyGLOBAL_API_KEY) once — it feeds both the gateway's defaultapikeyheader and the Go service's authentication.
Health checks
The main app's GET /health now includes a non-fatal whatsapp_service block:
{"configured": true, "reachable": true, "status": "ok"}
The WhatsApp service's own liveness endpoint is exposed through the gateway at
GET /api/whatsapp/server/ok.
Testing
python -m pytest tests/test_whatsapp_gateway.py -v
Covers: gateway auth, forwarding (method/path/query/body/headers), apikey
injection vs pass-through, upstream 4xx/5xx passthrough, 502/503/504 handling,
response-header sanitization and the /health integration.
A full stack test:
docker build -t agentdeck-backend:test .
# run with the WhatsApp env vars above + WHATSAPP_SERVICE_ENABLED=true