# MSG91 WhatsApp — New Webhook Setup Guide

Complete guide for configuring MSG91's **Webhook (New)** system to receive inbound WhatsApp messages and delivery failure notifications in the chatbot.

---

## Prerequisites

1. **MSG91 Account** with WhatsApp Business API enabled at [control.msg91.com](https://control.msg91.com)
2. **Chatbot Admin Settings** configured:
   - **MSG91 WhatsApp → Enable MSG91 Integration**: Checked
   - **Auth Key**: Your MSG91 `authkey` (from MSG91 dashboard → Developers → API Keys)
   - **Integrated WhatsApp Number**: Your number with country code (e.g. `919XXXXXXXXX`)
   - **Webhook Secret Token**: A long random secret value. Add this to every MSG91 webhook URL as `?token=YOUR_TOKEN`.
3. **Publicly accessible URL** — MSG91 webhooks cannot reach `localhost`. Use one of these:
   - Production server URL (e.g. `https://yourdomain.com/Chatbot`)
   - [ngrok](https://ngrok.com/) for local testing: `ngrok http 80` → use the `https://xxxx.ngrok-free.app` URL
   - [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) for persistent tunnels

---

## Step 1: Get Your Webhook URL

In the chatbot admin panel:

1. Go to **Settings → WhatsApp → MSG91 WhatsApp**
2. Click **"Get Webhook URL"** — a popup will display your webhook URL
3. Copy it. The format is:
   ```
   https://yourdomain.com/Chatbot/apps/whatsapp/post.php?token=YOUR_TOKEN
   ```
   > Replace `yourdomain.com/Chatbot` with your actual public domain and path. Replace `YOUR_TOKEN` with the exact **Webhook Secret Token** saved in the chatbot admin settings. Requests without a matching token are rejected with HTTP 403.

---

## Step 2: Create "Inbound Messages" Webhook (REQUIRED)

This webhook is **required** — it receives messages from WhatsApp users.

### Navigate to the Webhook Page

1. Log in to [MSG91 Dashboard](https://control.msg91.com)
2. Go to **WhatsApp** (left sidebar) → **Webhook**
3. Click the **"Webhook (New)"** tab (not "Webhook (Old)")
4. Click **"Create Webhook"**

### Configure the Webhook

| Field               | Value                                                  |
|---------------------|--------------------------------------------------------|
| **Webhook Name**    | `SolChat Inbound` (or any descriptive name)             |
| **Select Template** | `Custom Webhook`                                        |
| **Select Service**  | `Whatsapp`                                              |
| **Select Event**    | `On Inbound Request Received`                           |
| **Method**          | `POST`                                                  |
| **URL**             | `https://yourdomain.com/Chatbot/apps/whatsapp/post.php?token=YOUR_TOKEN` |
| **Content-Type**    | `JSON`                                                  |
| **Pause Webhook**   | Off                                                     |

### Webhook Payload (Body Tab)

Delete the default content and paste the following JSON. This payload template uses MSG91 template variables (`{{variable}}`) that get replaced with actual values when a message arrives:

```json
{
  "crqid": "{{crqid}}",
  "companyId": "{{companyId}}",
  "requestedAt": "{{requestedAt}}",
  "customerNumber": "{{customerNumber}}",
  "requestId": "{{requestId}}",
  "reason": "{{reason}}",
  "uuid": "{{uuid}}",
  "integratedNumber": "{{integratedNumber}}",
  "templateName": "{{templateName}}",
  "templateLanguage": "{{templateLanguage}}",
  "replyMsgId": "{{replyMsgId}}",
  "conversationExpTimestamp": "{{conversationExpTimestamp}}",
  "pluginsource": "{{pluginsource}}",
  "customerName": "{{customerName}}",
  "contentType": "{{contentType}}",
  "text": "{{text}}",
  "latitude": "{{latitude}}",
  "longitude": "{{longitude}}",
  "caption": "{{caption}}",
  "filename": "{{filename}}",
  "url": "{{url}}",
  "button": "{{button}}",
  "contacts": "{{contacts}}",
  "reaction": "{{reaction}}",
  "interactive": "{{interactive}}",
  "orders": "{{orders}}",
  "paymentStatus": "{{paymentStatus}}",
  "messageType": "{{messageType}}",
  "messages": "{{messages}}",
  "webhookType": "{{webhookType}}",
  "ts": "{{ts}}",
  "apiFailed": "{{apiFailed}}",
  "payload": "{{payload}}",
  "endpoint": "{{endpoint}}",
  "resStatusCode": "{{resStatusCode}}"
}
```

> **Note:** If MSG91 doesn't recognize a variable for a particular event, it may send it as an empty string or as the literal `{{variableName}}`. The chatbot code handles both cases automatically — unresolved template variables are filtered out.

### Field Descriptions

| Template Variable                | Purpose                                                      |
|----------------------------------|--------------------------------------------------------------|
| `{{crqid}}`                      | MSG91 correlation/request ID                                  |
| `{{companyId}}`                  | MSG91 company account ID                                      |
| `{{requestedAt}}`                | ISO timestamp when the message was received                   |
| `{{customerNumber}}`             | Customer's phone number with country code (e.g. `919876543210`) |
| `{{requestId}}`                  | MSG91 request/message ID                                      |
| `{{uuid}}`                       | Unique message identifier (fallback for requestId)            |
| `{{integratedNumber}}`           | Your integrated WhatsApp business number                      |
| `{{customerName}}`               | WhatsApp profile name of the sender                           |
| `{{contentType}}`                | Message type: `text`, `image`, `video`, `audio`, `document`, `sticker`, `location`, `contacts`, `button`, `interactive`, `reaction`, `orders` |
| `{{text}}`                       | Message text body (for text messages and captions)            |
| `{{url}}`                        | Direct download URL for media messages (image, video, audio, document, sticker) |
| `{{filename}}`                   | Original filename (for document messages)                     |
| `{{caption}}`                    | Caption text attached to media messages                       |
| `{{latitude}}`                   | Latitude for location messages                                |
| `{{longitude}}`                  | Longitude for location messages                               |
| `{{button}}`                     | Button reply payload (for button response messages)           |
| `{{contacts}}`                   | Contact card data (JSON) for contact-sharing messages         |
| `{{interactive}}`                | Interactive message reply data (list replies, button replies)  |
| `{{reaction}}`                   | Reaction emoji data for message reactions                     |
| `{{orders}}`                     | Order data for commerce/order messages                        |
| `{{paymentStatus}}`              | Payment status for payment-related messages                   |
| `{{messageType}}`                | Message type classification                                   |
| `{{messages}}`                   | Raw messages data (may contain additional message details)     |
| `{{templateName}}`               | Template name (if message relates to a template)              |
| `{{templateLanguage}}`           | Template language code                                        |
| `{{replyMsgId}}`                 | ID of the message being replied to (for reply context)        |
| `{{conversationExpTimestamp}}`    | Conversation expiration timestamp                             |
| `{{pluginsource}}`               | Plugin source identifier                                      |
| `{{webhookType}}`                | Webhook event type identifier                                 |
| `{{ts}}`                         | Unix timestamp                                                |
| `{{apiFailed}}`                  | Whether the API call failed (used for failure detection)      |
| `{{payload}}`                    | Original request payload (available on failure events)        |
| `{{endpoint}}`                   | API endpoint that was called (available on failure events)    |
| `{{resStatusCode}}`              | HTTP response status code (available on failure events)       |
| `{{reason}}`                     | Failure reason description                                    |

Click **"Save"** (or **"Create Webhook"**).

---

## Step 3: Create "Delivery Failures" Webhook (RECOMMENDED)

This webhook is **recommended** — it triggers the template fallback feature when a regular message delivery fails (e.g., outside the 24-hour window).

### Configure the Webhook

| Field               | Value                                                  |
|---------------------|--------------------------------------------------------|
| **Webhook Name**    | `SolChat Failures` (or any descriptive name)            |
| **Select Template** | `Custom Webhook`                                        |
| **Select Service**  | `Whatsapp`                                              |
| **Select Event**    | `On Failed Events`                                      |
| **Method**          | `POST`                                                  |
| **URL**             | `https://yourdomain.com/Chatbot/apps/whatsapp/post.php?token=YOUR_TOKEN` |
| **Content-Type**    | `JSON`                                                  |
| **Pause Webhook**   | Off                                                     |

### Webhook Payload (Body Tab)

Use the **same complete payload template** as the inbound webhook. The chatbot auto-detects failure events via the `apiFailed` and `webhookType` fields:

```json
{
  "crqid": "{{crqid}}",
  "companyId": "{{companyId}}",
  "requestedAt": "{{requestedAt}}",
  "customerNumber": "{{customerNumber}}",
  "requestId": "{{requestId}}",
  "reason": "{{reason}}",
  "uuid": "{{uuid}}",
  "integratedNumber": "{{integratedNumber}}",
  "templateName": "{{templateName}}",
  "templateLanguage": "{{templateLanguage}}",
  "replyMsgId": "{{replyMsgId}}",
  "conversationExpTimestamp": "{{conversationExpTimestamp}}",
  "pluginsource": "{{pluginsource}}",
  "customerName": "{{customerName}}",
  "contentType": "{{contentType}}",
  "text": "{{text}}",
  "latitude": "{{latitude}}",
  "longitude": "{{longitude}}",
  "caption": "{{caption}}",
  "filename": "{{filename}}",
  "url": "{{url}}",
  "button": "{{button}}",
  "contacts": "{{contacts}}",
  "reaction": "{{reaction}}",
  "interactive": "{{interactive}}",
  "orders": "{{orders}}",
  "paymentStatus": "{{paymentStatus}}",
  "messageType": "{{messageType}}",
  "messages": "{{messages}}",
  "webhookType": "{{webhookType}}",
  "ts": "{{ts}}",
  "apiFailed": "{{apiFailed}}",
  "payload": "{{payload}}",
  "endpoint": "{{endpoint}}",
  "resStatusCode": "{{resStatusCode}}"
}
```

### Failure-Specific Fields

| Template Variable       | Purpose                                                  |
|-------------------------|----------------------------------------------------------|
| `{{apiFailed}}`         | Set to a truthy value when the API call failed — primary failure indicator |
| `{{webhookType}}`       | Event type (e.g., `failed`) — secondary failure indicator |
| `{{reason}}`            | Human-readable failure reason description                 |
| `{{resStatusCode}}`     | HTTP response status code from the failed API call        |
| `{{payload}}`           | Original request payload that failed                      |
| `{{endpoint}}`          | API endpoint that was called                              |
| `{{customerNumber}}`    | Recipient phone number — used to trigger template fallback |

> **Note:** The chatbot detects failures when `apiFailed` is truthy OR `webhookType` contains "fail". It extracts the error details from `reason`, `payload`, or `text` fields and triggers template fallback logic for the affected conversation.

Click **"Save"** (or **"Create Webhook"**).

---

## Step 4: Verify the Setup

### Quick Test

1. Send a WhatsApp message from your phone to the integrated business number
2. Check the chatbot admin panel — a new conversation should appear with the message
3. Reply from the admin panel — the reply should arrive on your phone via WhatsApp

### Debug Logging

If messages don't appear, check the webhook log:

1. In the chatbot directory, check `logs/` folder for any error files
2. In the MSG91 dashboard, go to **Webhook (New)** → click on your webhook → **Logs** tab to see delivery attempts and response codes

### Expected Response

When the webhook is working correctly, MSG91 should receive an HTTP `200` response from your server. If it receives errors (4xx/5xx), MSG91 will retry the webhook delivery.

Send the same inbound payload twice during staging verification. The first request should create one chatbot message; the retry should receive HTTP `200` and should not create a duplicate message. The chatbot detects duplicates by the provider message ID (`requestId`, `uuid`, `crqid`, or deterministic fallback hash).

---

## Optional: Additional Webhooks

You may create addtional webhooks for extended tracking. These are **not required** for chatbot functionality but can be useful for monitoring:

| Event                          | Use Case                                    |
|--------------------------------|---------------------------------------------|
| `On Delivered Events`          | Track message delivery confirmations         |
| `On Read Events`               | Track when messages are read                 |
| `On Outbound Report Received`  | Detailed outbound delivery reporting         |
| `On Payment Report Received`   | Track payment link interactions (if using MSG91 payment feature) |

For these optional webhooks, use the same URL and the same complete payload template from Step 2 above. The chatbot only processes inbound and failure events — other events are received but ignored.

---

## How It Works (Technical Overview)

### Message Flow

```
User sends WhatsApp message
       ↓
MSG91 receives the message
       ↓
MSG91 "On Inbound Request Received" webhook fires
       ↓
POST to /apps/whatsapp/post.php with flat JSON payload
       ↓
post.php detects MSG91 new webhook format
  (checks: customerNumber present + entry[0].changes absent)
       ↓
Extracts fields: text, url, contentType, customerName,
  requestId/uuid, latitude, longitude, button, contacts,
  interactive, reaction, orders, caption, filename
       ↓
Transforms flat JSON → Meta Cloud API-compatible structure
  {messages: [...], contacts: [...], metadata: {...}}
       ↓
All downstream processing works unchanged:
  - User creation/lookup by phone number
  - Conversation creation/continuation
  - Attachment download (uses url directly if available)
  - Dialogflow/OpenAI bot responses
  - Agent notifications via Pusher
       ↓
Bot or agent replies via sb_msg91_send_message()
  → MSG91 Outbound API → WhatsApp → User
```

### Delivery Failure Flow (Template Fallback)

```
Agent/bot sends message via MSG91 API
       ↓
Delivery fails (e.g., 24h window expired)
       ↓
MSG91 "On Failed Events" webhook fires
       ↓
POST to /apps/whatsapp/post.php with failure JSON
  (apiFailed is truthy or webhookType contains "fail")
       ↓
post.php detects failure, transforms to Meta-compatible statuses[]
  Error details from: reason, payload, or text fields
  Error code from: resStatusCode field
       ↓
Existing template fallback logic triggers
  → Sends pre-approved template message instead
```

### Format Detection Logic

The chatbot auto-detects MSG91 new webhook format by checking:

1. `customerNumber` field exists in the JSON payload
2. Meta Cloud API `entry` field does NOT exist

This works independently of the configured WhatsApp provider — even if multiple providers are set up, MSG91 new webhook payloads are correctly identified and processed.

Unresolved MSG91 template variables (e.g., `{{templateName}}` sent literally) are automatically filtered out and treated as empty values.

---

## Localhost / Development Setup

MSG91's webhook system requires a **publicly reachable URL**. For local development:

### Using ngrok (Recommended for Testing)

1. Install [ngrok](https://ngrok.com/download)
2. Run:
   ```bash
   ngrok http 80
   ```
3. Copy the HTTPS forwarding URL (e.g., `https://abc123.ngrok-free.app`)
4. Set the webhook URL in MSG91 to:
   ```
   https://abc123.ngrok-free.app/Chatbot/apps/whatsapp/post.php?token=YOUR_TOKEN
   ```
5. **Important:** The ngrok URL changes every time you restart ngrok (free plan). Update the MSG91 webhook URL accordingly, or use a paid ngrok plan for a fixed subdomain.

### Using Cloudflare Tunnel

1. Install `cloudflared`
2. Run:
   ```bash
   cloudflared tunnel --url http://localhost:80
   ```
3. Use the generated URL as the webhook endpoint

---

## Troubleshooting

| Problem | Cause | Solution |
|---------|-------|----------|
| Messages not appearing in chatbot | Webhook URL not reachable | Verify the URL is publicly accessible (not localhost). Check MSG91 webhook logs for HTTP errors. |
| MSG91 webhook logs show 500 error | PHP error in post.php | Check your server's PHP error log and the `logs/` directory. |
| MSG91 webhook logs show 404 error | Wrong URL path | Verify the path matches your installation (e.g., `/Chatbot/apps/whatsapp/post.php`). |
| Media attachments not downloading | `url` not provided or expired | Ensure `{{url}}` is in the webhook payload. The chatbot falls back to `sb_msg91_get_media_url()` API if the direct URL is unavailable. |
| User shows as "User" instead of name | `customerName` not available | Verify that `{{customerName}}` template variable is included in the payload and is populated by MSG91. |
| Template fallback not triggering | "On Failed Events" webhook not created | Create the delivery failures webhook (Step 3) and ensure `{{apiFailed}}` and `{{webhookType}}` fields are in the payload. |
| Duplicate messages | Multiple webhooks configured or payload missing all stable ID fields | Check that only one inbound webhook is active for the event and include at least one of `{{requestId}}`, `{{uuid}}`, `{{crqid}}`, or `{{replyMsgId}}`. The chatbot also uses a deterministic fallback hash for MSG91 retry payloads. |
| MSG91 webhook logs show 403 | Missing or wrong token query parameter | Add `?token=YOUR_TOKEN` to the webhook URL, matching the **Webhook Secret Token** in chatbot admin settings. |
| `{{variableName}}` appears in logs | MSG91 doesn't recognize the variable | The chatbot filters unresolved template variables automatically. If a critical field (like `text` or `customerNumber`) isn't resolved, the variable name may not be available for the selected event — check MSG91 docs for the correct variable name. |

---

## IP Allowlist Decision

Do not enable a hardcoded MSG91 IP allowlist unless MSG91 provides stable, documented source IP ranges for your exact account/region and the hosting firewall can be updated without downtime. The required production control is the shared webhook token in the URL. If MSG91 later provides reliable IP ranges, add them at the web server or firewall layer as an additional control, not as a replacement for the token.

---

## Summary Checklist

- [ ] MSG91 Integration enabled in chatbot admin settings
- [ ] Auth Key configured
- [ ] Integrated WhatsApp Number configured (with country code)
- [ ] Webhook Secret Token configured and appended to both webhook URLs as `?token=...`
- [ ] **Webhook 1** created: `On Inbound Request Received` → your public webhook URL
- [ ] **Webhook 2** created: `On Failed Events` → your public webhook URL (recommended)
- [ ] Both webhooks use `POST` method, `JSON` content type
- [ ] Both webhook payloads use the JSON templates from this guide
- [ ] Duplicate/retry test performed: same payload posted twice creates only one chatbot message
- [ ] Tested: sent a WhatsApp message → appeared in chatbot admin panel
- [ ] Tested: replied from admin panel → received on WhatsApp
