# WhatsApp Workflow Audit

## Audit Session - 2026-05-29 11:38:13 +03:00

## Current Workflow Table

| Step | Current Behavior | Expected Behavior | Problem Found | Recommendation |
|---|---|---|---|---|
| 1. Customer sends WhatsApp message | Meta sends webhook to `/api/webhooks/whatsapp`. | Receive verified incoming message. | Depends on `WHATSAPP_APP_SECRET` and verify token being configured. | Add setup/health checks for WhatsApp credentials. |
| 2. Backend receives message | `WhatsAppWebhookController` extracts `entry.0.changes.0.value`. | Parse all relevant messages/statuses safely. | Only first entry/change/message is handled. | Later support multiple messages/entries if needed. |
| 3. Signature verification | `VerifyWhatsAppSignature` checks `X-Hub-Signature-256`. | Reject unsigned/invalid webhooks. | Good if app secret is configured. | Keep; add admin status warning if missing. |
| 4. Idempotency | `WebhookEvent::create()` uses message ID as event key; duplicates return OK. | Avoid duplicate processing. | Good for message webhooks. | Also document behavior for status-only webhooks. |
| 5. Contact matching | `Contact::where('wa_id', $waId)->lockForUpdate()->first()`. | Match by stable WhatsApp sender ID/phone. | Good; contact `wa_id` is unique. | Keep; normalize phone if required. |
| 6. Contact creation/update | Creates contact if missing; updates profile name if changed. | Create or update customer profile. | Contact profile name is encrypted, but search may be limited by encryption. | Consider searchable normalized fields if needed. |
| 7. Conversation matching | Finds latest conversation for contact, regardless of status; reopens if closed. | Reuse existing conversation or create a new workflow item according to business rules. | Prevents future duplicates, but historical duplicates already exist. | Add cleanup/merge plan for duplicate historical conversations. |
| 8. Assignment/routing | New conversation is assigned to manager/admin through `ConversationRouter`. | Either assign by routing rules or send to waiting list if unassigned. | Waiting list is bypassed; dedicated contact agent is ignored. | Define routing: manager triage, dedicated agent, round-robin, or waiting list. |
| 9. Message save | Saves incoming message with unique `wa_message_id`, type/body/media/payload. | Persist message idempotently. | Good basic coverage. | Add media download/preview flow later. |
| 10. Unread/new message | Increments `unread_count`, updates `last_message_at`, broadcasts event, sends database notification. | Show unread/new message indicators. | UI still polls; no Echo subscription in dashboard. | Keep polling for now, later wire real-time safely. |
| 11. Employee opens conversation | Dashboard fetches messages and calls mark-read. | Mark unread as read for authorized user. | Works conceptually; active message loading reloads messages repeatedly. | Avoid unnecessary reloads after real-time is added. |
| 12. Employee replies | `MessageController@store` creates pending outgoing message, sends via Cloud API, marks sent/failed. | Save reply and send to WhatsApp. | Requires valid access token and phone number ID. Attachments are not implemented. | Add credential checks and media send support. |
| 13. Assignment/transfer | Dashboard and Filament actions call transfer endpoint or direct table action. | Admin/supervisor transfer according to role rules. | Backend lets agents transfer own conversations; some UI target lists show all active users. | Align backend and UI with expected roles. |
| 14. Waiting/unassigned | `pending` status exists and queue UI exists. | Unassigned conversations should be visible in Waiting List. | Incoming workflow creates `open` conversations assigned to manager/admin, not pending/unassigned. | Implement explicit waiting-list state and assignment action. |

## Current Data Findings Before Test DB Reset

| Finding | Evidence |
|---|---|
| Contacts are unique by `wa_id`. | 2 contacts existed. |
| Historical duplicate conversations existed for the same contact. | Contact `201151322294` had 4 conversations, 3 closed and 1 open. |
| New duplicate prevention appears implemented. | Job now reuses latest conversation for contact. |
| Waiting list had no rows. | Pending filter showed no conversations. |
| Assignment history existed. | 7 assignment rows existed before reset. |

## Key Files Reviewed

- `routes/api.php`
- `app/Http/Controllers/WhatsAppWebhookController.php`
- `app/Http/Middleware/VerifyWhatsAppSignature.php`
- `app/Jobs/ProcessIncomingMessageJob.php`
- `app/Jobs/UpdateMessageStatusJob.php`
- `app/Services/ConversationRouter.php`
- `app/Services/WhatsAppCloudApiClient.php`
- `app/Http/Controllers/MessageController.php`
- `app/Http/Controllers/ConversationController.php`
- `app/Events/NewMessageEvent.php`
- `routes/channels.php`

## Workflow Problems

| Problem | Impact | Recommended Fix |
|---|---|---|
| Waiting List is not wired into routing | Queue screen can be empty while incoming chats are assigned to manager/admin. | Decide and implement unassigned/pending intake workflow. |
| Dedicated agent is configurable but unused | Admin may assign a dedicated agent to a contact but new conversations ignore it. | Use `contacts.dedicated_agent_id` in `ConversationRouter`. |

## Phase 3A Update - 2026-05-29 12:30:00 +03:00

| Step | Current Behavior | Expected Behavior | Problem Found | Recommendation |
|---|---|---|---|---|
| Customer sends WhatsApp message | Webhook remains idempotent by message ID. | Duplicate webhook deliveries should not duplicate messages/conversations. | None found in tests. | Keep `WebhookEvent` unique guard and message unique guard. |
| Find/create contact | Contact is found by `wa_id`; existing contact is reused. | Match by WhatsApp sender ID/phone. | No duplicate contacts found in Phase 3A tests. | Continue using `wa_id` as primary match key. |
| Find/create conversation | Existing latest conversation for contact is reused; pending receives new messages without duplicate rows. | Pending/open flows should update same conversation. | Historical duplicate cleanup remains separate. | Add cleanup/merge plan later if needed. |
| Dedicated agent routing | Active dedicated agent gets an open assigned conversation. Inactive dedicated agent sends conversation to waiting list with team scope. | Dedicated active employee should be preferred. | Fixed in `ConversationRouter`. | Keep target validation to active `agent` role. |
| No valid route | New conversation becomes `pending` with `assigned_to = null`. | Unknown/unrouted messages should appear in Waiting List. | Fixed. | Admin triages global unscoped waiting conversations. |
| Waiting List assignment | Admin/supervisor can assign according to role scope; assignment creates Assignment Log and opens conversation. | Assignment should move row out of Waiting List and into employee conversations. | Fixed and tested. | Add optional assignment note/comment later if business needs it. |

| Historical duplicate conversations remain | Customer appears multiple times in lists/history. | Add a controlled merge/archive plan for duplicates. |
| Agent transfer allowed by backend | Employee can transfer via API despite expected behavior. | Update policy and tests. |
| Attachments are UI-only | Users can click attachment options but real media sending is incomplete. | Add WhatsApp media upload/send APIs. |
| Real-time backend exists but frontend polls | Slower updates and repeated API load. | Later wire Echo/Reverb after permission model is fixed. |
