# WhatsApp (Infobip) setup

Uses the official PHP client: [`infobip/infobip-api-php-client`](https://github.com/infobip/infobip-api-php-client) (see [WhatsApp quickstart](https://github.com/infobip/infobip-api-php-client/blob/master/whatsapp.md)).

Configure day-to-day credentials in **Admin → Settings → WhatsApp (Infobip)** (admin and developer roles). `.env` values are optional fallbacks.

## TehRon Infobip account (current)

| Setting | Value |
|---------|-------|
| Base URL | `https://4kwnk1.api.infobip.com` |
| WhatsApp sender | `+44 7860 088970` → `447860088970` |
| Number type | UK virtual long number (shared) |
| Inbound keyword | `TECHRONLTDKE` |
| Webhook path | `/api/webhooks/infobip/whatsapp` |
| PHP SDK | `infobip/infobip-api-php-client` ^6.2.1 |

## API key scopes (required)

The Infobip API key must include WhatsApp scopes (send + template management). Without them, calls return **403 Unauthorized**.

In Infobip portal → **API keys** → edit **TehRon Support** → enable WhatsApp-related scopes → save. Then re-test.

## Connectivity / send test

```bash
cd backend

# Verify credentials + list templates for the sender
php artisan whatsapp:test --check

# Send an approved template from this sender (trial: Infobip predefined, not ticket_created)
php artisan whatsapp:test --to=254791660680

# Force a specific template (only works if it exists on this sender)
php artisan whatsapp:test --to=254791660680 --template=connect_to_bot

# Free-form text (only after the recipient messaged you within 24 hours)
php artisan whatsapp:test --to=254791660680 --text="TechRon WhatsApp test"
```

Or set `INFOBIP_TEST_TO=254791660680` in `.env` and omit `--to`.

Admin **Send test** lists Infobip templates and sends an **approved predefined** text template (prefers `ticket_created` only if it exists, otherwise `connect_to_bot`). Trial / shared sender `447860088970` cannot register custom `ticket_*` templates — those names return Meta **7009 MEDIA_TEMPLATE**.

The settings panel shows available Infobip templates plus status (`PENDING` / `REJECTED` / `UNDELIVERABLE`), message id, and raw payload. HTTP 200 only means Infobip accepted the request — it is not WhatsApp delivery.

### Trial limits

- **Whitelist**: trial can only send to the one phone verified at Infobip signup. `254791660680` reached Meta; `254793878068` fails with `REJECTED_DESTINATION_NOT_REGISTERED` / code 579 until Support or a paid account lifts the restriction.
- **Templates**: shared sender only has Infobip’s predefined templates. Custom `ticket_created` will not deliver on trial.
- **Production ticket notifications** still use `ticket_*` names. Register a **dedicated** WhatsApp sender (Channels → WhatsApp → Register sender), then create the UTILITY templates below.

## `.env` placeholders

```
INFOBIP_BASE_URL=https://4kwnk1.api.infobip.com
INFOBIP_API_KEY=
INFOBIP_WHATSAPP_FROM=447860068926
INFOBIP_WEBHOOK_SECRET=
INFOBIP_TEMPLATE_LANGUAGE=en
INFOBIP_INBOUND_KEYWORD=TECHRONLTDKE
INFOBIP_HTTP_VERIFY=true
INFOBIP_TEST_TO=
INFOBIP_TEMPLATE_TICKET_CREATED=ticket_created
INFOBIP_TEMPLATE_TICKET_ASSIGNED=ticket_assigned_client
INFOBIP_TEMPLATE_TECHNICIAN_ASSIGNED=ticket_assigned_technician
INFOBIP_TEMPLATE_TICKET_REPLIED=ticket_reply_alert
INFOBIP_TEMPLATE_TICKET_CLOSED=ticket_closed
INFOBIP_TEMPLATE_STATUS_CHANGED=ticket_status_update
INFOBIP_TEMPLATE_TICKET_UPDATED=ticket_general_update
```

Local SSL inspection: set `INFOBIP_HTTP_VERIFY=false` only if needed.

## Message templates to register (UTILITY)

Create these on the dedicated sender `447860068926` (TechRon Limited WABA) after Meta approval. Language must match exactly (`en` vs `en_US`). Category **UTILITY**.

**Assigned templates must be text-only:** Header = **None**, no IMAGE/DOCUMENT/VIDEO header, no side-colored buttons. Media assigned templates are skipped at send time.

**Placeholders must appear in strictly ascending order in the body text** (`{{1}}` before `{{2}}` before `{{3}}`) — Meta rejects a template outright if a higher-numbered placeholder appears before a lower one. A placeholder also can't be the very last thing in the template (real words must follow it).

The ticket number ({{2}}) only appears in `ticket_created` — it's the one message that introduces the ticket. Every later notification identifies the ticket by subject instead, since the full thread is already visible in the portal.

The event key on the left is the internal identifier used in code (`WhatsAppSettings::TEMPLATE_KEYS`, `WhatsAppNotifier`); the template name on the right is what's registered with Meta — they no longer have to match 1:1, so a template can be renamed/resubmitted without touching notification code.

1. `ticket_created` (event: `ticket_created`, `en_US`) — Hello {{1}}, your TechRon support ticket {{2}} has been created. Subject: {{3}}. Reply to this chat for updates. *(APPROVED)*
2. `ticket_assigned_client` (event: `ticket_assigned`, `en_US`) — Hi {{1}}, your TechRon support ticket has been assigned to {{2}}. Subject: {{3}}. Reply to this chat for updates. *(APPROVED)*
3. `ticket_assigned_technician` (event: `technician_assigned`, `en_US`) — Hi {{1}}, you have been assigned a TechRon support ticket. Subject: {{2}}. Priority: {{3}}. Reply to this chat for updates. *(APPROVED)*
4. `ticket_reply_alert` (event: `ticket_replied`, `en_US`) — Hi {{1}}, you have a new reply on your TechRon support ticket: {{2}}. Reply to this chat to respond. *(APPROVED)*
5. `ticket_closed` (event: `ticket_closed`, `en`) — Hi {{1}}, your ticket for {{3}} is now {{2}}. Thanks for your patience! *(In review — resubmitted directly through the Infobip portal under this name, not `ticket_closed_alert`)*
6. `ticket_status_update` (event: `status_changed`, `en`) — Hello {{1}}, your ticket status is now {{2}}. The subject is: {{3}}. Thank you. *(APPROVED)*
7. `ticket_general_update` (event: `ticket_updated`, `en`) — Dear {{1}}, we are writing to inform you that your ticket has been updated: {{2}}. Please let us know if you have any further questions. *(APPROVED)*

**Status as of 2026-09-04:** templates 2–4 and 6–7 above were resubmitted through the Infobip portal (with different wording than originally drafted here — content doesn't have to match this doc exactly, only placeholder count/order matters to the code) and are now **APPROVED**. Template 5 (`ticket_closed`) is still "In review" under that exact name — the account never approved a `ticket_closed_alert`, so the code (`config/infobip.php`, `.env`) and stored `SystemSetting` now point at `ticket_closed` to match what actually exists, rather than resubmitting again under the old convention.

**Per-template language matters.** Four templates were registered under `en_US`, three under `en` — Infobip requires an exact match between the language passed in the send request and the template's registered language, or the send fails even for an approved template. `WhatsAppSettings::templateLanguage(string $key)` now resolves the correct language per event key (`infobip.template_languages` config, overridable via `SystemSetting` as `whatsapp_template_language_{key}`), and `WhatsAppNotifier::sendToUser()` passes it explicitly — previously every notification used the single global `INFOBIP_TEMPLATE_LANGUAGE` value (`en`), which silently mismatched the four `en_US` templates.

See [Infobip template docs](https://www.infobip.com/docs/whatsapp/message-types-and-templates/message-templates).

## Runtime notes

- Enable the channel in Admin Settings, then use **Send test** / `whatsapp:test --check`. Check the Infobip status on the panel or CLI — `REJECTED` / `UNDELIVERABLE` means the phone will not get the message.
- **`Destination not registered` / `REJECTED_DESTINATION_NOT_REGISTERED`**: Infobip trial can only send to the single phone verified at signup. Send the test to that number (`254791660680`), or add funds / contact Infobip Support to allow other recipients.
- **`7009 MEDIA_TEMPLATE`**: the template name/language does not exist on this sender, or a media template was sent without a header. On trial, do not send `ticket_created`.
- Ticket WhatsApp notifies are queued (`dispatch()`). Keep `php artisan queue:work` and `php artisan schedule:work` running so mail, WhatsApp, IMAP poll, WhatsApp inbox retry, SLA escalate, and manager digest leave the request. `composer dev` starts both. Inbound messages that fail processing are retried by `whatsapp:retry-inbox`. If an inbound keyword is set, new chats without that word are ignored.
- Users need a phone number on their profile to receive WhatsApp.
- Client WhatsApp replies are stored in `ticket_messages` (`channel=whatsapp`).
- Staff still reply from the portal; technicians receive WhatsApp notifications only.
