# WhatsApp Web call integration (Spec 004 + Spec 005) This document describes the call-control and call-media surface added by Spec 004 (call control, catalog/orders, `/chat/reply`, webhook v1) and Spec 005 (call-media sidecar bridge). ## Design constraints honored - **No new environment variable.** Nothing here reads `os.Getenv`. - **No new database table.** No migration was added; `migrations.go` is unchanged. Call state is **not** persisted. - **Webhook contract intact.** Enrichment is nested under a single additive `v1` key (see `docs/webhook-events.md`). - **Honest failure modes.** Endpoints return `501`/`503` truthfully instead of pretending a capability exists. ## 1. Call control (Spec 004) Always available — no feature flag. | Endpoint | Method | Behavior | |-----------------------|--------|---------------------------------------------------------------------| | `/call/reject/send` | POST | Existing endpoint, unchanged. | | `/call/reject` | POST | Reject + optional follow-up text. No persistence. | | `/call/accept` | POST | Send accept stanza. | | `/call/preaccept` | POST | Send preaccept (ringing) stanza. | | `/call/terminate` | POST | Send terminate stanza (default reason `hangup`). | | `/call/initiate` | POST | **501 Not Implemented** — originating a call needs a WebRTC media stack not present in this build. Honest stub. | There is **no** `/call/history` and **no** `/call/missed` — those required a `call_log` table, which is intentionally not added. ## 2. Catalog / products / orders (Spec 004) `/business/order/{orderID}`, `/business/catalog/{businessJID}`, `/business/products/{businessJID}`, `/business/collections/{businessJID}`, `/business/catalog/send`, `/business/product/send`, `/business/product-list/send`, `/business/shop/send`. - Catalog reads are cached in-memory for 300s (a constant, not an env var). - Catalog/product/collection reads return **503 `catalog_unavailable`** until the upstream read stanza is validated (honest stub). - `/business/shop/send` is gated by the project's **existing** enterprise license mechanism (`ensureEnterpriseLicense`, the same gate as carousel/flows/interactive) and returns **403** when not licensed. No new env var, no new license key. ## 3. Call media — sidecar bridge (Spec 005) > ⚠️ **Superseded for real audio.** For working two-way call audio use the validated > Go-native path documented in **[`call-audio-implementation-guide.md`](./call-audio-implementation-guide.md)** > (PCM over `/call/{id}/stream` + browser AudioWorklet, confirmed by the meowcaller author and > proven in production on funnelchat-api). The sidecar below (`dinastiapi-voip-sidecar`, > `@w3nder/whatsapp-voip-wasm`) is a `v0.1.0` scaffold shipping placeholder SDP and is **not** > the recommended way to get audio. Keep it only if a separate non-meowcaller media backend is > genuinely required. DinastiAPI only **proxies signaling** to an external WebRTC sidecar. The sidecar is a separate process/repo; this build never embeds media. ### 3.1 Architecture ``` Operator browser ── HTTPS ──> DinastiAPI /call/media/* ──> per-user sidecar (HTTP, X-Sidecar-Token) │ └── webhook v1.call.* enriches the call lifecycle ``` ### 3.2 Per-user sidecar configuration (no env, no schema) The project persists per-user transport config (RabbitMQ, S3) as `users` table columns via migrations. Adding sidecar columns would require a migration, which the zero-schema constraint forbids. Therefore the sidecar config is **per-user, held in memory**, configured at runtime: | Endpoint | Method | Behavior | |---------------------------|--------|-------------------------------------------------------| | `/session/sidecar/config` | POST | Set `{enabled, url, token}` for the calling user. | | `/session/sidecar/config` | GET | Returns `{enabled, url, has_token}` (token masked). | | `/session/sidecar/config` | DELETE | Clears the user's sidecar config. | ```bash curl -X POST $BASE/session/sidecar/config \ -H "token: $USER_TOKEN" -H "Content-Type: application/json" \ -d '{"enabled":true,"url":"http://127.0.0.1:7700","token":"shared-secret"}' ``` Configuration is in-memory: it is per-user and is re-applied by the operator after a restart (no env var, no schema migration). This is the deliberate, zero-schema design. ### 3.3 Media endpoints | Endpoint | Method | Behavior | |--------------------------------------------|--------|---------------------------------------------------| | `/call/media/{call_id}/operator-sdp` | POST | Proxy operator SDP offer/answer to the sidecar. | | `/call/media/{call_id}/operator-ice` | POST | Proxy an operator ICE candidate. | | `/call/media/{call_id}/operator-attach` | POST | Signal the sidecar to attach the operator PC. | | `/call/media/{call_id}/hangup` | POST | Tear down the media session (returns `duration_ms`). | ### 3.4 Honest 503 semantics - **No sidecar configured for the user** → `503 sidecar_not_configured` with a hint to call `POST /session/sidecar/config`. - **Sidecar configured but unreachable / 5xx** → `503 sidecar_unreachable` (with `retry_after_seconds` on operator-sdp). The media endpoints never fake success. Until an operator configures a reachable sidecar, every media call answers 503 truthfully. ## 4. curl examples ```bash # Reject an incoming call with a follow-up message curl -X POST $BASE/call/reject -H "token: $USER_TOKEN" \ -d '{"call_id":"","call_from":"","reject_type":"declined","message":"Retorno em breve"}' # Operator SDP (requires a configured, reachable sidecar; else 503) curl -X POST $BASE/call/media//operator-sdp -H "token: $USER_TOKEN" \ -d '{"sdp":"","type":"offer"}' # Hangup curl -X POST $BASE/call/media//hangup -H "token: $USER_TOKEN" ``` ## 5. See also - `docs/webhook-events.md` — the `v1` webhook enrichment schema. - `static/api/spec.yml` — OpenAPI definitions for every endpoint above.