Files
Adolfo Delorenzo 2dc94682ff docs(02-03): complete WhatsApp channel adapter plan
- Create 02-03-SUMMARY.md documenting WhatsApp adapter implementation
- Update STATE.md: advance progress to 56%, add 4 key decisions, record metrics
- Update ROADMAP.md: Phase 2 plan progress updated
- Mark CHAN-03, CHAN-04 requirements complete in REQUIREMENTS.md
2026-03-23 14:44:49 -06:00

8.1 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
02-agent-features 03 messaging
whatsapp
meta-cloud-api
hmac
webhook
minio
normalization
business-scoping
media
phase provides
01-foundation KonstructMessage model, channel gateway, normalize.py, resolve_tenant, check_rate_limit, is_duplicate/mark_processed
WhatsApp Business Cloud API channel adapter (packages/gateway/gateway/channels/whatsapp.py)
normalize_whatsapp_event() normalizing Meta Cloud API v20.0 payloads to KonstructMessage
MediaAttachment and MediaType models in shared/models/message.py
media: list[MediaAttachment] field added to MessageContent
verify_whatsapp_signature() HMAC-SHA256 on raw body bytes
verify_hub_challenge() for Meta webhook registration handshake
is_clearly_off_topic() tier 1 keyword scoping gate
build_off_topic_reply() canned redirect message builder
send_whatsapp_message() and send_whatsapp_media() outbound delivery
Media download from Meta API + MinIO storage with tenant-prefixed keys
WhatsApp routes registered in gateway main.py
whatsapp_app_secret, whatsapp_verify_token, MinIO settings added to shared/config.py
02-agent-features/02-05 — outbound response routing in orchestrator tasks.py must route whatsapp channel through send_whatsapp_message
02-agent-features — any plan touching gateway/normalize.py should import MediaAttachment/MediaType
added patterns
boto3 (not yet in pyproject.toml — added inline in media download function, needs uv add)
httpx (already in gateway deps — used for Meta API calls)
Raw body read BEFORE JSON parsing for HMAC verification (prevent tampered-body attack)
verify_whatsapp_signature returns raw bytes on success, raises HTTPException(403) on failure
verify_hub_challenge returns challenge string on success, raises HTTPException(403) on failure
normalize_whatsapp_event: meta-media://{media_id} placeholder URL before actual download
Two-tier scoping: keyword gate (no LLM) -> LLM with system prompt scoping
Always return 200 OK to Meta regardless of processing errors (Meta retry prevention)
thread_id = sender wa_id for WhatsApp (no threading concept in WhatsApp)
created modified
packages/gateway/gateway/channels/whatsapp.py
tests/unit/test_whatsapp_verify.py
tests/unit/test_whatsapp_normalize.py
tests/unit/test_whatsapp_scoping.py
packages/shared/shared/models/message.py
packages/shared/shared/config.py
packages/gateway/gateway/normalize.py
packages/gateway/gateway/main.py
HMAC uses hmac.new() (not hmac.HMAC()) for timing-safe signature verification via hmac.compare_digest
meta-media://{media_id} placeholder URL set at normalization time; actual download in adapter after tenant resolution
thread_id set to sender wa_id — WhatsApp conversation scope is per-phone-number, not thread-based
Always return HTTP 200 to Meta webhooks regardless of processing errors to prevent Meta retry storms
Tier 1 scoping uses word-level tokenization with set intersection for keyword overlap check
MinIO settings added to shared/config.py alongside WhatsApp credentials for use by all services
boto3 required for MinIO access (S3-compatible client); needs uv add boto3 in gateway package before prod
WhatsApp adapter pattern: verify signature on raw bytes -> normalize -> tenant resolve -> rate limit -> dedup -> scope -> media -> dispatch
Business-function scoping: tier 1 (keyword) rejects clearly off-topic without LLM; tier 2 (LLM) enforces via system prompt
Canned redirect format: '{agent_name} is here to help with {topics}. How can I assist you with one of those?'
CHAN-03
CHAN-04
7min 2026-03-23

Phase 2 Plan 03: WhatsApp Channel Adapter Summary

WhatsApp Business Cloud API adapter with HMAC-SHA256 verification, Meta v20.0 normalization, two-tier business-function scoping gate, and MinIO media storage

Performance

  • Duration: ~7 minutes
  • Started: 2026-03-23T20:36:19Z
  • Completed: 2026-03-23T20:43:00Z
  • Tasks: 2
  • Files modified: 7 (4 created, 3 modified)

Accomplishments

  • WhatsApp Business Cloud API adapter with full inbound webhook processing pipeline
  • HMAC-SHA256 signature verification on raw body bytes (timing-safe via hmac.compare_digest)
  • normalize_whatsapp_event() for text, image, and document messages to KonstructMessage
  • Two-tier Meta 2026 business-function scoping: keyword gate + LLM system prompt scoping
  • MediaAttachment model with typed media fields added to shared message model
  • Media download from Meta Graph API and storage in MinIO with tenant-prefixed keys
  • Outbound delivery functions for text and media via Meta Cloud API
  • 54 new passing tests (8 verify + 22 normalize + 14 scoping)

Task Commits

Each task was committed atomically:

  1. Task 1: Media model extension, WhatsApp normalizer, and signature verification - 370a860 (feat)
  2. Task 2: WhatsApp adapter with business-function scoping, media download/storage, and outbound delivery - 6fea34d (feat)

Note: TDD tasks - tests written first then implementation

Files Created/Modified

  • packages/gateway/gateway/channels/whatsapp.py - Full WhatsApp adapter: signature verification, hub challenge, webhook handler, scoping, media, outbound delivery
  • packages/gateway/gateway/normalize.py - Added normalize_whatsapp_event() alongside existing Slack normalizer
  • packages/shared/shared/models/message.py - Added MediaType enum, MediaAttachment model, media field on MessageContent
  • packages/shared/shared/config.py - Added whatsapp_app_secret, whatsapp_verify_token, MinIO settings
  • packages/gateway/gateway/main.py - Registered whatsapp_router, updated docstring
  • tests/unit/test_whatsapp_verify.py - 8 tests for signature verification and hub challenge
  • tests/unit/test_whatsapp_normalize.py - 22 tests for normalization (text, image, document)
  • tests/unit/test_whatsapp_scoping.py - 14 tests for tier 1 scoping gate and canned reply

Decisions Made

  • HMAC uses hmac.new() with hmac.compare_digest for timing-safe comparison, preventing timing oracle attacks
  • Normalizer sets meta-media://{media_id} as placeholder URL — actual download occurs in the adapter after tenant resolution, so the download has access to the access_token from channel_connections
  • thread_id = sender wa_id — WhatsApp has no threading; conversation scope is per phone number
  • Always return HTTP 200 to Meta even on processing errors — Meta retries all non-200 responses which can cause duplicate processing
  • Tier 1 scoping uses word-level tokenization (text.split()) with set intersection, not substring matching, to avoid false positives
  • MinIO settings co-located in shared/config.py so all services (gateway, orchestrator) can access MinIO

Deviations from Plan

None - plan executed exactly as written.

Issues Encountered

  • Gateway package was not installed in the venv (only shared and orchestrator were). Running uv sync --all-packages resolved the import error. This is a Rule 3 (blocking) auto-fix.

User Setup Required

The following environment variables must be added to .env before WhatsApp webhooks can receive real traffic:

WHATSAPP_APP_SECRET=<from Meta Developer Console>
WHATSAPP_VERIFY_TOKEN=<your chosen verification token>
MINIO_ENDPOINT=http://localhost:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_MEDIA_BUCKET=konstruct-media

Also required before production: uv add boto3 in packages/gateway/ for MinIO uploads.

Next Phase Readiness

  • WhatsApp adapter is ready for tenant registration via channel_connections with phone_number_id as workspace_id
  • Outbound response routing (LLM-generated replies via send_whatsapp_message) must be wired in Plan 02-05 (orchestrator tasks.py)
  • MinIO bucket konstruct-media must be created before media messages can be processed

Phase: 02-agent-features Completed: 2026-03-23