Phase 3 — Self-Service WhatsApp-Channel-Onboarding

Overview

Bauen die Pipeline die einen neuen Kunden in unter 5 Minuten von „interessiert” auf „bot live auf eigener WhatsApp-Business-Nummer” bringt — ohne dass wir oder der Kunde im Facebook Business Manager rumstochern muessen.

Voraussetzung: 360dialog Partner-Plan Growth (€500/Mo) ist aktiviert. Dieser Plan baut die Infrastruktur drumherum so dass die Aktivierung des Partner-Vertrags der einzige verbleibende manuelle Schritt ist — alles andere ist Self-Service.

Problem Frame

Heute braucht jeder neue WhatsApp-Kunde 1-2 Wochen Setup-Krampf (Meta Business Manager anlegen, Phone-Number-Verify, App Review, Tech-Provider-Antrag). Das bremst Akquise messbar: Lead 3 ist in aktiver Verhandlung (siehe origin _index.md Sequencing), aber „2 Wochen Setup”-Story koennte ihn kippen. Phase 3 ersetzt das durch Embedded Signup: Kunde klickt einen Link, durchlaeuft 3-4 Browser-Schritte (eigener Meta-Login + Business-Manager-Wahl + Phone-Number-Verify + Permission-Grant), und 360dialog meldet uns per Webhook „Channel aktiv”. Wir provisionieren automatisch: Token in Secrets Manager, Row in receptionist-customers, Bot ist live.

Requirements Trace

Aus dem origin-Doc _index.md Phase 3 + Sequencing:

  • R1. Kunde durchlaeuft Embedded Signup auf onboarding.agenticventures.de ohne unsere manuelle Beteiligung
  • R2. Nach erfolgreicher 360dialog-Channel-Aktivierung wird automatisch eine neue Row in receptionist-customers (DDB) angelegt
  • R3. Channel-API-Key wird automatisch in AWS Secrets Manager unter receptionist/tenant/<slug>/360dialog-api-key abgelegt
  • R4. receptionist-brain kann den neuen Tenant sofort bedienen (DDB-Stream-Trigger funktioniert ohne Code-Aenderung)
  • R5. Local-Storage-Setting der Phone-Number auf EU (Germany) wird im Channel-Setup automatisch gesetzt (DSGVO-Pflicht aus Research-Run)
  • R6. Onboarding-Session wird in DDB mit Audit-Trail vorgehalten (welcher Lead hat wann gestartet, mit welcher Email)
  • R7. Bei Embedded-Signup-Abbruch oder Failure ist Session sauber abgeraeumt (TTL 7 Tage) und kann wieder gestartet werden
  • R8. 360dialog-Webhook-Aufrufe sind signaturverifiziert (kein anonymer POST darf eine Channel-Provisionierung ausloesen)
  • R9. Reconciliation-Job laeuft alle 5-15 Min und gleicht Partner-API-Channel-Liste mit receptionist-customers ab (Webhook-Verlust-Schutz)

Success Criteria (aus Sequencing): Lead 3-Onboarding-Flow in der vereinbarten Live-Woche durchlaeuft komplett autonom — End-to-End-Zeit Browser-Klick bis Bot-Antwort unter 5 Minuten.

Scope Boundaries

  • Out-of-Scope: mcp-whatsapp Multi-Provider-Refactor (= Phase 1 des Migrations-Projekts, separater Plan, hier nur Interface-Anforderung dokumentiert)
  • Out-of-Scope: Icking-Migration Direct-Client → Partner-Hub (passiert nach Aktivierung als 1-Tag-Folge-Arbeit, eigene PR)
  • Out-of-Scope: Friseur-Migration auf 360dialog (bleibt Meta-Direct als Resilienz-Hedge, Phase 4 im origin)
  • Out-of-Scope: Partner-Plan-Vertragsabschluss bei 360dialog (manueller Sales-Schritt, Trigger fuer Aktivierung dieses Plans)
  • Out-of-Scope: Reseller-Billing / eigene Marge-Rechnung an Endkunden (kommt spaeter mit Partner-paid Billing-Modell)
  • Non-Goal: Eigene Inbox-UI fuer Mitarbeiter — wir bleiben „Bot-only”, Kunden mit menschlichem Posteingang-Bedarf bekommen separat Superchat-Empfehlung

Context & Research

Relevant Code and Patterns

  • Receptionist-Schema [receptionist.md](../../capabilities/receptionist.md): PK phone_number_id (S), YAML-Config, state.active Toggle. Phase-3-Erweiterung: 3 neue Top-Level-Felder (provider, provider_credentials_secret_arn, local_storage_region). Aktueller einziger Eintrag: Friseur im Sueden (Meta-Direct, bleibt unangetastet).
  • Agents-Platform-Stack-Pattern [agents-platform.md](../../capabilities/agents-platform.md): ~/source/agents-platform/infra/lib/<name>-stack.ts, kopieren von beleg-pipeline-stack.ts oder receptionist-brain-stack.ts. Lambda-Layer agentic-common fuer Telegram/Bedrock/Secrets/Logging.
  • mcp-whatsapp Webhook-Pattern [mcp-whatsapp.md](../../capabilities/repos/mcp-whatsapp.md): bestehende Routes POST /webhook (Meta) + GET /webhook (Verify). FastMCP custom_route() Pattern. Wir ergaenzen POST /webhook/360dialog analog.
  • Onboarding-Page-Hosting: av-cockpit Pattern (Next.js Static Export → S3 + CloudFront mit Cloudflare-Edge davor). Siehe intern/firma/web-properties.md + dashboard.agenticventures.de-Setup.
  • Fargate-Tunnel-Pattern [mcp-hosting-fargate-tunnel.md](../../wissen/prozesse/mcp-hosting-fargate-tunnel.md): nur dann noetig wenn wir tatsaechlich Container brauchen. Fuer reines Static-Site + Webhook-Lambda nicht relevant — leichter mit S3+CloudFront + Lambda Function URL hinter Cloudflare.
  • DDB-Cross-Account-Pattern: wir bleiben innerhalb av-production (425924867359, eu-central-1) — keine Cross-Account-AssumeRoles fuer Phase 3 noetig.

Institutional Learnings

  • AVV bereits gesichert: assets/legal/360dialog/2025-04-360dialog-AVV-Partner-Klienten.pdf ist die richtige Variante fuer das Partner-Modell. EU-Verarbeitungs-Klausel auf Seite 3 §5 — DSGVO-Lage ist erledigt.
  • Local-Storage-Default EU (Germany) muss bei jedem Channel-Setup explizit gesetzt werden (ist NICHT Default in 360dialog). Steht im Research-Run.
  • Service-Window-Quirk: Outbound-free-form-Messages funktionieren nur in offenem 24h-Service-Window. Erstes Outbound braucht approved Template. Gilt fuer +1 555 Test-Nummer wie fuer echte Channels. Smoke-Skript muss das beruecksichtigen.
  • FastMCP DNS-Rebinding-Quirk: falls Onboarding-Backend doch FastMCP-basiert wird → MCP_ALLOWED_HOSTS Env-Var pflicht (siehe mcp-hosting-fargate-tunnel.md). Voraussichtlich aber Lambda + reines HTTP, keine FastMCP, kein Quirk.

External References

Aus Research-Run 2026-05-18-research-360dialog-whatsapp-bsp/_index.md:

Key Technical Decisions

  • Erweiterung von receptionist-customers statt neue av-channel-registry-Table: eine Source-of-Truth, Brain liest schon dort. Korrigiert die Brainstorm-Annahme.
  • Webhook-Route am mcp-whatsapp-Service statt eigenem Webhook-Service: spart Hosting + nutzt den existierenden Fargate-Tunnel + selbe Sicherheits-Boundary. Route POST /webhook/360dialog analog zu bestehendem Meta-Webhook.
  • Onboarding-Page als Next.js Static + S3+CloudFront + Cloudflare-Edge: kein neues Container-Hosting noetig, wiederverwendet av-cockpit-Pattern. API-Routes (Session-Start, Status-Polling) als separate Lambda Function URL hinter Cloudflare (Pattern mcp-whatsapp hat schon Cloudflare-Tunnel, aber Function URL ist einfacher fuer reine HTTP-Endpoints).
  • onboarding-sessions als neue DDB-Table mit TTL 7 Tage: Audit-Trail (Lead-Email, Start-Timestamp, 360dialog client_id, Status) + Korrelation Browser-Flow ↔ Webhook-Callback. Nicht in receptionist-customers mergen weil Lifecycle anders ist (Sessions koennen verfallen ohne Channel zu werden).
  • Webhook + Reconciliation kombiniert: Webhook ist primary (low-latency Provisionierung), Reconciliation-Lambda alle 15 Min raeumt verpasste Webhooks auf. Pattern wie Stripe. Spart uns Production-Incidents wenn 360dialog mal einen Webhook verliert.
  • Provider-Discrimination in receptionist-customers.provider Feld (meta-direct | 360dialog): erlaubt Friseur (Meta-Direct) + alle Neuen (360dialog) parallel. mcp-whatsapp Phase-1-Refactor muss das Feld lesen.
  • Webhook-Signatur-Pflicht: 360dialog signiert Webhooks mit HMAC. Verifikation am Endpoint pflicht — kein anonymer POST darf receptionist-customers schreiben.
  • Embedded-Signup-Flow vollstaendig clientseitig getriggert: Onboarding-Page laedt 360dialog-Embedded-Signup-Widget (JS-SDK), Browser redirected zu Meta, kommt mit Permission-Code zurueck, JS submitet zu unserer API-Lambda die den 360dialog Partner-API-Call macht. Wir agieren als Proxy, nicht als eigener Auth-Provider.

Open Questions

Resolved During Planning

  • Wo wohnt die Onboarding-Page? Antwort: S3 + CloudFront unter onboarding.agenticventures.de, analog dashboard.agenticventures.de. Cloudflare-Edge davor fuer TLS + WAF.
  • Webhook-Hosting? Antwort: bestehender mcp-whatsapp-Service kriegt zusaetzliche Route. Spart eine neue Komponente.
  • Neue Table oder Erweiterung? Antwort: Erweiterung von receptionist-customers + separate onboarding-sessions-Table fuer den temporaeren Flow-State.
  • Webhook oder Polling? Antwort: Beides. Webhook primary, Reconciliation als Backup.
  • Partner-Plan-Aktivierung als Teil des Plans? Nein — manueller Schritt, ist Trigger fuer Plan-Aktivierung. Plan baut Infrastruktur die ohne Partner-Plan-Live-Schaltung schon „bereit” steht (Sandbox-Test-Mode).

Deferred to Implementation

  • Exakte 360dialog-Webhook-Signatur-Verifikation (HMAC-SHA-256 vs anderes?): Doku lesen + implementieren in Unit 3
  • Lambda Function URL CORS-Konfiguration fuer Cross-Origin-POSTs von der Onboarding-Page: einrichten in Unit 4
  • Genaue Felder im 360dialog Channel-Status-Webhook-Payload: erst beim ersten Test-Channel sichtbar, Schema-Lese-Code in Unit 3 toleranter Bauen
  • Visual Design der Onboarding-Page: copy + Layout, koennte als spaetere ce:work-Iteration ausgefeilt werden. Erste Version funktional ausreichend.
  • Idempotency-Key-Strategie bei Webhook-Replay: vermutlich event_id als DDB-Conditional-Write — in Unit 3 implementieren
  • Reconciliation-Cron-Intervall: 5 vs 15 Min vs 1h. In Unit 5 starten mit 15 Min, anpassen wenn Webhook-Verluste auftauchen

High-Level Technical Design

Diese Skizze illustriert den angestrebten Flow und ist Direktional, keine Implementierungs-Spezifikation. Der implementierende Agent behandelt sie als Kontext, nicht als Code zum Reproduzieren.

flowchart TB
    L[Lead klickt Onboarding-Link in Email/Chat] --> P[onboarding.agenticventures.de Static Next.js]
    P --> A1[POST /api/start-onboarding Lambda Function URL]
    A1 --> S[onboarding-sessions DDB Row mit session_id Status pending]
    A1 --> CP[POST partners/pid/clients via 360dialog Partner-API]
    CP --> ESU[Embedded Signup URL]
    ESU --> P
    P --> M[Browser Redirect zu Meta Embedded Signup]
    M --> MC{Kunde durchlaeuft Meta-Flow}
    MC -->|Success| CB[Browser Callback an Onboarding-Page]
    MC -->|Abbruch| ABRT[Session bleibt pending, TTL 7d raeumt auf]
    CB --> A2[POST /api/finalize-onboarding Lambda]
    A2 --> CC[POST partners/pid/clients/id/channels mit local_storage EU]
    CC --> WAIT[Status pending_channel_activation]
    WAIT --> W{360dialog Webhook ankommend}
    W --> WH[POST /webhook/360dialog Route in mcp-whatsapp]
    WH --> SIG[HMAC Signatur verifizieren]
    SIG -->|valid| PROV[Provisionierungs-Lambda]
    PROV --> SM[Channel-API-Key in Secrets Manager]
    PROV --> RC[Row in receptionist-customers anlegen]
    PROV --> SU[onboarding-sessions Status active]
    PROV --> TG[Telegram-Push an Marvin neuer Channel live]
    REC[Reconciliation-Cron alle 15 Min] --> A3[GET partners/pid/clients/id/channels]
    A3 --> DIFF{Channel aktiv aber keine receptionist-customers Row}
    DIFF -->|ja| PROV

Flow-Notizen:

  • Session-State lebt nur in onboarding-sessions bis Channel aktiv, dann wird sie active markiert (Audit) und faellt unter TTL raus
  • PROV-Lambda ist die einzige Stelle die receptionist-customers schreibt — sowohl Webhook als auch Reconciliation rufen dieselbe Funktion auf (Idempotency via channel_id-Conditional-Write)
  • mcp-whatsapp ist nur Webhook-Empfaenger + Signatur-Verifier; Business-Logik liegt in der Provisionierungs-Lambda (agents-platform Stack)
  • Receptionist-Brain bleibt unangetastet — neue Row in receptionist-customers triggert nichts direkt; Brain reagiert erst wenn End-Kunde an die neue Nummer schreibt

Implementation Units

  • Unit 1: receptionist-customers Schema-Erweiterung + onboarding-sessions Table

Goal: DDB-Schema erweitern damit Provider-Info pro Tenant abgelegt werden kann, plus neue Session-Table fuer den temporaeren Onboarding-Flow.

Requirements: R2, R5, R6, R7

Dependencies: keine (DDB Schemas sind schemalos, kein Migrations-Risk)

Files:

  • Modify: ~/source/agents-platform/infra/lib/receptionist-brain-stack.ts (neue Table onboarding-sessions ergaenzen)
  • Modify: ~/source/agents-platform/lambdas/receptionist-brain/state.py (lese-Helper fuer neue Felder, defensiv mit Default-Werten)
  • Create: ~/source/agents-platform/docs/receptionist-customers-schema.md (Schema-Doku mit neuen Feldern dokumentieren)
  • Modify: intern/capabilities/receptionist.md (Schema-Section + neue Table ergaenzen)

Approach:

  • receptionist-customers bleibt PK phone_number_id — neue optionale Top-Level-Felder: provider (default meta-direct), provider_credentials_secret_arn (default null fuer Friseur), local_storage_region (default eu-germany)
  • onboarding-sessions neue Table: PK session_id (UUID), TTL-Attribute expires_at (7d), Felder lead_email, started_at, dialog360_client_id, status (pending | pending_channel_activation | active | failed), failure_reason
  • PITR an fuer onboarding-sessions (Audit-Pflicht)
  • state.py Helper get_customer_with_provider(phone_number_id) returnt typisiertes Object mit Provider-Info — Friseur-Row bekommt automatisch meta-direct als Default

Patterns to follow:

  • Bestehende DDB-Tables im ReceptionistBrainStack als Vorlage (4 Tables, PITR-Toggle, TTL-Setup)
  • Schemas in YAML-Frontmatter dokumentieren wie aktuell receptionist.md macht

Test scenarios:

  • Happy path: bestehende Friseur-Row liest sauber mit neuem Helper, provider ist meta-direct per Default
  • Happy path: neuer Test-Eintrag mit provider: 360dialog + provider_credentials_secret_arn: arn:... wird gelesen
  • Edge case: Row ohne provider-Feld (alte Row) → Helper liefert meta-direct als Default, kein Crash
  • Integration: CDK-Deploy ergaenzt onboarding-sessions Table ohne bestehende Tables anzufassen (drift-check)

Verification:

  • cdk diff zeigt nur ADD von onboarding-sessions-Table + keine Modify auf receptionist-customers
  • cdk deploy erfolgreich, aws dynamodb describe-table --table-name onboarding-sessions zeigt TTL aktiv
  • state.py-Unit-Test gruen fuer alle 3 Scenarios

  • Unit 2: 360dialog Partner-API-Client als Lambda-Layer-Erweiterung

Goal: Wiederverwendbarer Client fuer 360dialog Partner-API (Client-Anlage, Channel-Polling, Embedded-Signup-URL-Generation) im agentic-common Layer.

Requirements: R1, R5, R9

Dependencies: Unit 1 (Schema-Felder muessen feststehen damit Client weiss was er provisioniert)

Files:

  • Create: ~/source/agents-platform/layers/agentic-common/python/agentic_common/dialog360.py
  • Create: ~/source/agents-platform/layers/agentic-common/python/agentic_common/tests/test_dialog360.py
  • Modify: ~/source/agents-platform/layers/agentic-common/python/agentic_common/__init__.py (Export)

Approach:

  • Client-Class Dialog360PartnerClient mit Methoden: create_client(name, email) -> client_id, generate_embedded_signup_url(client_id, redirect_uri) -> url, list_channels(client_id) -> list[Channel], get_channel(client_id, channel_id) -> Channel
  • Partner-API-Key + Partner-ID aus Secrets Manager (Cached pro Lambda-Cold-Start)
  • HTTP-Client httpx (passt zu existierenden Lambdas), Timeout 10s, Retry mit Backoff bei 5xx
  • Base-URL aus Env (DIALOG360_PARTNER_API_BASE, Default https://hub.360dialog.io/api/v2)
  • Sandbox vs Production via Env-Var trennen (Partner-Antrag ist noch nicht durch, bis dahin laeuft alles gegen Sandbox)
  • Local-Storage-Region beim Channel-Setup auf eu-germany hardcoded — Helper-Methode set_channel_local_storage(channel_id, region)

Patterns to follow:

  • agentic_common.bedrock und agentic_common.secrets als Stil-Vorlage (Class-mit-Cache, Lambda-Cold-Start-aware)
  • Pytest-Tests mit httpx_mock (kein echter Netz-Call in Unit-Tests)

Test scenarios:

  • Happy path: create_client returnt client_id aus stub 200-Response
  • Happy path: list_channels parst Response-Liste korrekt
  • Error path: 401 von Partner-API → Dialog360AuthError mit klarer Message
  • Error path: 5xx → 3 Retries dann Dialog360ApiError
  • Error path: Timeout → klar erkennbar in Logs
  • Integration: gegen 360dialog-Sandbox-Account ein echter Smoke-Test (nicht in CI, manuell vor Unit-3-Bau)

Verification:

  • pytest layers/agentic-common/python/agentic_common/tests/test_dialog360.py gruen, 6+ Tests
  • Manueller Smoke gegen Sandbox: create_client + list_channels returnt erwartete Werte
  • Code-Review: kein API-Key oder Partner-ID in Log-Output (Sanitization-Check)

  • Unit 3: Provisionierungs-Lambda + 360dialog-Webhook-Route in mcp-whatsapp

Goal: Webhook-Route die signaturverifiziert eingehende 360dialog-Lifecycle-Events annimmt + Provisionierungs-Lambda die Channel-API-Key in Secrets Manager schreibt und receptionist-customers-Row anlegt.

Requirements: R2, R3, R4, R5, R8

Dependencies: Unit 1 (Schema steht), Unit 2 (Client verfuegbar)

Files:

  • Modify: ~/source/mcps/mcp-whatsapp/src/mcp_whatsapp/server.py (neue custom_route POST /webhook/360dialog)
  • Create: ~/source/mcps/mcp-whatsapp/src/mcp_whatsapp/dialog360_webhook.py (Signatur-Verify + Dispatch)
  • Create: ~/source/agents-platform/lambdas/onboarding-provisioner/main.py
  • Create: ~/source/agents-platform/infra/lib/onboarding-stack.ts (neuer CDK-Stack)
  • Create: ~/source/agents-platform/lambdas/onboarding-provisioner/tests/test_main.py
  • Modify: ~/source/agents-platform/infra/bin/app.ts (Stack-Instanziierung)

Approach:

  • mcp-whatsapp Route POST /webhook/360dialog: Signatur-Verify (HMAC-SHA-256 mit Partner-Webhook-Secret), Event-Type-Switch (channel.activated, channel.deactivated, channel.error)
  • Bei channel.activated: SQS-Message an Provisionierungs-Queue mit client_id + channel_id + phone_number_id + session_id (aus Webhook-Payload)
  • Provisionierungs-Lambda SQS-getriggert (Async-Decoupling, Webhook-Endpoint antwortet sofort 200 — wichtig damit 360dialog nicht retried)
  • Provisionierungs-Lambda:
    1. Holt Channel-Details via Partner-API (get_channel) — inkl. API-Key
    2. Schreibt API-Key in Secrets Manager receptionist/tenant/<slug>/360dialog-api-key
    3. Conditional-Write in receptionist-customers mit PK phone_number_id und provider=360dialog, provider_credentials_secret_arn=<arn>, local_storage_region=eu-germany, state.active=false (manuell aktivieren erst nach Marvin-Pruefung)
    4. Updated onboarding-sessions[session_id].status = active
    5. Setzt Local-Storage auf eu-germany via Partner-API (falls noch nicht geschehen)
    6. Telegram-Push an Marvin: „Neuer Channel live: / <phone_number>”
  • Idempotency: channel_id als Idempotency-Key, Conditional-Write attribute_not_exists(channel_id)

Execution note: Start with characterization-Test fuer Signatur-Verify (Edge: invalid signature, replay attack, missing header) bevor andere Logik gebaut wird. Sicherheits-kritischer Code zuerst test-first.

Patterns to follow:

  • mcp-whatsapp bestehende POST /webhook als Vorlage fuer FastMCP custom_route
  • BelegPipelineStack als CDK-Stack-Vorlage fuer onboarding-stack.ts (Lambda + SQS + DDB-Permissions + Secrets-Permissions)
  • agentic_common.telegram fuer den Push

Test scenarios:

  • Happy path: valider Webhook channel.activated → SQS-Message landet
  • Happy path: SQS-Trigger → Secrets Manager hat neuen Key, receptionist-customers hat neue Row, onboarding-sessions Status active, Telegram-Push erfolgt
  • Edge case: gleicher Webhook zweimal (Replay) → zweite Provisionierung ist no-op (Conditional-Write greift)
  • Edge case: Webhook fuer unbekannte session_id → in Log, kein Crash, kein Schreiben in DDB
  • Error path: invalid HMAC-Signatur → 401, kein Schreiben in SQS
  • Error path: fehlender Signature-Header → 401
  • Error path: Partner-API-Call fuer get_channel schlaegt fehl → Lambda retried via SQS-Visibility-Timeout, nach 3 Fails in DLQ
  • Error path: Secrets-Manager-Write fehlschlaegt → DLQ, Telegram-Alarm
  • Integration: End-to-End in Sandbox — Webhook stub → SQS → Lambda → Secrets + DDB. Manuelle Run, nicht CI.

Verification:

  • pytest lambdas/onboarding-provisioner/tests/ gruen
  • cdk deploy OnboardingStack erfolgreich
  • Webhook-Smoke mit curl + valider HMAC liefert 200; mit invalider HMAC 401
  • aws dynamodb scan --table-name receptionist-customers zeigt nach Smoke neue Row

  • Unit 4: Onboarding-API-Lambda (Session-Start + Finalize) mit Function URL

Goal: Backend-API die vom Onboarding-Page-JS aufgerufen wird — startet Onboarding-Session, gibt Embedded-Signup-URL zurueck, finalisiert nach Browser-Callback.

Requirements: R1, R6, R7

Dependencies: Unit 1 (Sessions-Table existiert), Unit 2 (Partner-API-Client verfuegbar)

Files:

  • Create: ~/source/agents-platform/lambdas/onboarding-api/main.py
  • Create: ~/source/agents-platform/lambdas/onboarding-api/tests/test_main.py
  • Modify: ~/source/agents-platform/infra/lib/onboarding-stack.ts (Lambda + Function URL ergaenzen)

Approach:

  • Single-Lambda mit Routen-Switch (kein API Gateway noetig — Function URL ist ausreichend, CORS konfigurierbar)
  • Routen:
    • POST /api/start-onboarding mit {lead_email, customer_slug, persona_name, business_name} → erstellt Session, calls create_client, returnt {session_id, embedded_signup_url}
    • GET /api/onboarding-status?session_id=... → returnt aktuellen Status (pending | pending_channel_activation | active | failed)
  • CORS: Origin https://onboarding.agenticventures.de whitelisten, andere refusen
  • Rate-Limit: 5 Requests/Min/IP via Lambda-eigene DDB-Counter (oder Cloudflare-WAF davor, eleganter)
  • Cloudflare-Worker oder -Rule davor um die Lambda-Function-URL nicht direkt exposeable zu machen (Domain api.onboarding.agenticventures.de → Cloudflare-Tunnel oder Worker-Proxy)

Patterns to follow:

  • Lambda-Function-URL mit AuthType=NONE + CORS in CDK (addFunctionUrl({ authType: lambda.FunctionUrlAuthType.NONE, cors: {...} }))
  • agentic_common.logging fuer strukturiertes Logging mit request_id

Test scenarios:

  • Happy path: valide POST /api/start-onboarding → 200 mit session_id + URL, DDB-Row angelegt
  • Happy path: GET status returnt aktuellen Wert
  • Edge case: POST mit fehlendem lead_email → 400 mit Fehlerbeschreibung
  • Edge case: 6. Request in einer Minute → 429
  • Error path: Partner-API down → 503, Session-Row mit Status failed und failure_reason
  • Error path: bekannter Lead startet Session erneut → neue Session-Row (kein Duplicate-Check, Sessions sind separate Events)
  • Integration: vollstaendiger Flow Lambda → Partner-API-Sandbox → DDB

Verification:

  • Unit-Tests gruen
  • curl von einer fremden Origin wird per CORS abgelehnt
  • curl von onboarding.agenticventures.de Origin erlaubt
  • DDB-Row enthaelt erwartete Felder nach Smoke

  • Unit 5: Reconciliation-Cron-Lambda

Goal: Alle 15 Min Channel-Liste vom Partner-API holen und mit receptionist-customers abgleichen — verpasste Webhooks aufraeumen.

Requirements: R9

Dependencies: Unit 2 (Partner-Client), Unit 3 (Provisionierungs-Lambda existiert + ist idempotent)

Files:

  • Create: ~/source/agents-platform/lambdas/onboarding-reconciler/main.py
  • Create: ~/source/agents-platform/lambdas/onboarding-reconciler/tests/test_main.py
  • Modify: ~/source/agents-platform/infra/lib/onboarding-stack.ts (EventBridge-Cron + Lambda)

Approach:

  • EventBridge-Rule rate(15 minutes) → Lambda
  • Lambda: list_channels fuer alle bekannten client_ids aus onboarding-sessions (Status != active)
  • Pro Channel: Wenn Channel aktiv und receptionist-customers hat noch keine Row → SQS-Message an Provisionierungs-Queue (Unit 3 Idempotency greift)
  • Telegram-Push wenn Reconciliation Channels findet die nicht durch Webhook kamen (frueh-Warn fuer Webhook-Probleme)

Patterns to follow:

  • agent-daily-briefing als EventBridge-Cron-Lambda-Vorlage
  • AgenticVenturesAgent-Construct nutzen wenn moeglich

Test scenarios:

  • Happy path: alle Sessions sind active → kein Schreiben, Telegram-Stille
  • Happy path: 1 Channel aktiv aber nicht in receptionist-customers → SQS-Message + Telegram-Notice
  • Edge case: keine pending Sessions → fruehes Return
  • Error path: Partner-API down → Log + Telegram, kein Crash
  • Integration: gegen Sandbox mit manuell „verpasstem” Channel — Reconciler fix das auf

Verification:

  • Unit-Tests gruen
  • CloudWatch Logs zeigen 15-Min-Rythmus
  • Telegram-Notice kommt bei Test-Szenario

  • Unit 6: Onboarding-Page Next.js Static

Goal: Browser-Seite die Lead den 4-Schritt-Flow durchklicken laesst — Form fuer Customer-Daten, 360dialog Embedded Signup-Widget-Einbettung, Status-Polling nach Callback.

Requirements: R1, R7

Dependencies: Unit 4 (API-Lambda existiert)

Files:

  • Create: ~/source/agentic-ventures-website/app/onboarding/whatsapp/page.tsx (oder neues ~/source/av-onboarding/ Repo — TBD beim Bau)
  • Create: ~/source/agentic-ventures-website/app/onboarding/whatsapp/_components/SignupForm.tsx
  • Create: ~/source/agentic-ventures-website/app/onboarding/whatsapp/_components/EmbeddedSignupTrigger.tsx
  • Create: ~/source/agentic-ventures-website/app/onboarding/whatsapp/_components/StatusPolling.tsx
  • Create: ~/source/agentic-ventures-website/app/onboarding/whatsapp/_components/SuccessPage.tsx
  • Modify: CDK-Stack der av-website wenn separate Subdomain noetig (oder direkt als Route in der bestehenden Site, Subdomain-Cutover spaeter)

Approach:

  • Schritt 1: Form lead_email, customer_slug, persona_name, business_name → submit ruft Unit-4-Lambda
  • Schritt 2: Embedded-Signup-URL wird in neuem Tab oder iframe geoeffnet (360dialog-Doku entscheidet was best practice ist — vermutlich Pop-up wegen Meta-OAuth)
  • Schritt 3: Polling-Status-Page (alle 3s GET /api/onboarding-status bis active oder failed, max 10 Min)
  • Schritt 4: Success-Page mit naechsten Schritten („Wir haben dir eine Bestaetigung an geschickt”) oder Failure-Page mit Re-Try
  • Kein User-Login auf der Page (Embedded-Signup-Token reicht als Identitaets-Indikator)
  • Brand-Kit aus agentic-ventures-brand-kit falls verfuegbar (siehe Routing)

Patterns to follow:

  • av-cockpit als Next.js Static Export Vorlage
  • Tailwind v4 + bestehender av-website Look-and-Feel

Test scenarios:

  • Happy path: Form-Submit funktioniert, Embedded-Signup-Popup oeffnet
  • Happy path: Polling erkennt active-Status und zeigt Success
  • Edge case: Polling-Timeout nach 10 Min → klare Fehlermeldung mit Support-Email
  • Edge case: Form-Validation: leere Felder, ungueltige Email
  • Error path: API antwortet 5xx → Retry-Button mit Fehlermeldung
  • Integration: End-to-End in Sandbox (Marvin onboardet sich selbst als Test-Channel)

Verification:

  • Static-Export erfolgreich (pnpm build)
  • S3-Deploy + CloudFront-Invalidation laeuft
  • https://onboarding.agenticventures.de liefert die Page
  • Manueller End-to-End-Test in Sandbox erfolgreich

  • Unit 7: End-to-End-Smoketest in Sandbox + Doku

Goal: Kompletter Self-Onboarding-Flow von Marvin selbst durchgespielt + dokumentiert, fuer Lead-3-Pitch-Demo.

Requirements: R1-R9 (alle)

Dependencies: Units 1-6 deployed

Files:

  • Create: intern/runs/<datum>-smoke-onboarding-self-test/_index.md
  • Modify: intern/projekte/whatsapp-bsp-migration/_index.md (Phase-3-Status aktualisieren)
  • Modify: intern/capabilities/receptionist.md (Provider-Pattern dokumentieren)

Approach:

  • Marvin startet selbst einen Onboarding-Flow mit Test-Email + Test-Channel im Sandbox
  • Tracking: jede Stage abhaken, Edge-Cases triggern (Browser-Schliessen, Re-Try, double-Webhook)
  • Cleanup: Test-Channel manuell wieder entfernen in 360dialog-Sandbox
  • Doku: Run-Report mit Screenshots/Logs, Liste der Friktionen die Lead 3 sehen wuerde

Test scenarios:

  • Happy path: kompletter Flow unter 5 Minuten
  • Edge case: Browser mitten im Meta-Flow geschlossen → Reconciliation fix das
  • Edge case: Webhook 30 Sek verzoegert → User sieht Polling-Pending dann Success
  • Integration: WhatsApp-Inbound an die neue Test-Nummer wird vom Receptionist-Brain beantwortet (Smoke fuer R4)

Verification:

  • Run-Report in intern/runs/ mit Outcome
  • Lead-3-Demo-Ready-Status im _index.md gesetzt

System-Wide Impact

  • Interaction graph:
    • mcp-whatsapp bekommt zweite Webhook-Route — bestehende Meta-Route bleibt unangetastet
    • receptionist-brain bekommt neue Rows in receptionist-customers — Brain-Code bleibt unangetastet, nur Reader-Helper bekommt neue Felder
    • Lambda Function URL bekommt erstmal eigene URL (nicht hinter Cloudflare-Tunnel) — Cloudflare-Worker-Proxy als optionale Haertung wenn noetig
  • Error propagation:
    • Webhook-Failures → SQS-Retry → DLQ → Telegram-Push → Reconciliation als Fallback
    • Embedded-Signup-Abbruch → Session bleibt pending, TTL raeumt nach 7d auf, Lead kann Re-Try
    • Partner-API-Outage → Lambda-Failures bleiben in DLQ, Reconciler holt nach wenn API wieder da
  • State lifecycle risks:
    • Doppel-Onboarding derselben Phone-Number: Conditional-Write in receptionist-customers mit PK phone_number_id greift (kein Duplicate)
    • Session expired aber Channel ist aktiv: Reconciliation findet Channel und erstellt Row trotzdem (Audit-Trail dann ohne session_id-Korrelation)
  • API surface parity:
    • mcp-whatsapp Multi-Provider-Refactor (Phase 1) MUSS bevor Phase 3 live geht durch sein — Brain redet sonst weiter Meta-Direct auch fuer neue 360dialog-Channels
    • mcp-whatsapp Schnittstelle zur Brain bleibt gleich (send_text(phone_number_id, text)) — interne Provider-Diskriminierung
  • Integration coverage:
    • End-to-End-Smoke (Unit 7) ist die einzige echte Pruefung — Unit-Tests pro Lambda decken nicht die Cross-Service-Interaktion
  • Unchanged invariants:
    • Friseur (Meta-Direct, Phone-Number 1170948102758111) bleibt unangetastet — provider: meta-direct als Default sichert Backward-Compat
    • Bestehende mcp-whatsapp Meta-Webhook-Route bleibt 1:1
    • Receptionist-Brain Tool-Loop bleibt unangetastet

Risks & Dependencies

RiskMitigation
Partner-Antrag bei 360dialog dauert > 2 WochenPhase-3-Bau ist Antrag-unabhaengig — Sandbox-Mode genuegt fuer Build + Test. Aktivierung wartet auf Vertrag.
Lead 3 dropt mid-BuildBau ist sunk cost €0 zusaetzlich (kein Partner-Plan vor Aktivierung). Plan bleibt zukunftsfaehig fuer naechsten Lead.
Embedded-Signup-Spec aendert sich bei Meta360dialog ist der Buffer — sie passen ihr Widget an. Unsere API-Calls sind 360dialog-API-stabil.
Webhook-Signatur-Doku unvollstaendig oder anders als erwartetUnit 3 startet mit Signatur-Verify als Characterization-Test, kein Build vorwaerts bis Verify gruen ist
360dialog Sandbox != Production (Schema-Differenzen)Unit 7 End-to-End-Smoke explizit gegen Sandbox; nach Partner-Vertrag-Aktivierung erneut Smoke gegen Production-Test-Channel BEVOR Lead 3 onboarden darf
Lambda Function URL ist exponiert (Brute-Force)Rate-Limit + CORS am Lambda; optional Cloudflare-Worker-Proxy als Haertung in Iteration 2
Sensitive Daten (Partner-API-Key) in CloudWatchSanitization-Helper in agentic_common.logging (Liste verbotener Felder) + Code-Review
Code-Verrottung durch Meta-API-Drift (HeyJulia-Lesson 2025: Cloud API v18 → v23 brach ohne Hinweis die alte Integration)360dialog-Hub-URL waba-v2.360dialog.io ist versions-stabil — 360dialog uebernimmt die Meta-Versions-Mappings intern. Alle neuen Channels gehen daher ueber 360dialog. Friseur (Meta-Direct) bleibt aus Resilienz-Hedge-Grund, sollte aber im Q3-Q4 2026 migriert werden bevor naechster Versions-Break (Phase 4 origin).

Documentation / Operational Notes

  • Docs to update nach Plan-Abschluss:
    • intern/capabilities/receptionist.md Schema-Section + Provider-Pattern
    • intern/capabilities/repos/mcp-whatsapp.md neue Route ergaenzen
    • intern/capabilities/agents-platform.md Liste der aktiven Agents um onboarding-provisioner, onboarding-api, onboarding-reconciler erweitern
    • intern/firma/web-properties.md onboarding.agenticventures.de ergaenzen
    • intern/wissen/prozesse/ neuer Eintrag „360dialog-Partner-Onboarding-Flow” als wiederverwendbarer Pattern fuer kuenftige BSP-Integrationen
  • Operational:
    • Telegram-Notice fuer Channel-Aktivierungs-Events (informativ, kein Page)
    • CloudWatch-Alarm auf SQS-DLQ-Tiefe > 0 (Page wenn Provisionierung dauerhaft scheitert)
    • CloudWatch-Alarm auf Lambda-Function-URL 5xx-Rate > 10%/5min
    • Partner-API-Key Rotation: jaehrlich, Reminder als TODO in intern/firma/active-work.md
  • Rollout:
    • Sandbox-Test mit eigenem Channel
    • Production-Smoke nach Partner-Vertrag-Aktivierung mit dediziertem Test-Channel
    • Erst dann Lead-3-Onboarding-Link rausgeben

Sources & References