# WABA RabbitMQ Messaging Contract

# WABA RabbitMQ Messaging Contract

# Nexgate ↔ WABA Gateway Service

> Exchange: `whatsapp.exchange` (topic)

---

## Overview

```
Meta/WhatsApp
      │
      ▼
WABA Gateway Service          ← owns Meta API, webhook endpoint
      │  publishes inbound events
      ▼
whatsapp.exchange
      │
      ├── whatsapp.inbound.#   → q.whatsapp.inbound   → Nexgate consumes
      ├── whatsapp.outbound.#  → q.whatsapp.outbound  → WABA Gateway consumes
      └── whatsapp.outbound.auth → q.whatsapp.auth    → WABA Gateway consumes

```

> **Note:** Inbound/outbound are from **Nexgate's perspective**.
> 
> - `inbound` = WABA Gateway publishes → Nexgate consumes
> - `outbound` = Nexgate publishes → WABA Gateway consumes

---

## Exchange &amp; Queues

<table id="bkmrk-name-type-purpose-wh"><thead><tr><th>Name</th><th>Type</th><th>Purpose</th></tr></thead><tbody><tr><td>`whatsapp.exchange`</td><td>topic</td><td>Single shared exchange</td></tr><tr><td>`q.whatsapp.inbound`</td><td>durable</td><td>Messages coming IN to Nexgate</td></tr><tr><td>`q.whatsapp.outbound`</td><td>durable</td><td>Messages going OUT from Nexgate (shop chatbot)</td></tr><tr><td>`q.whatsapp.auth`</td><td>durable</td><td>OTP delivery for registration and login</td></tr></tbody></table>

---

## Routing Keys

<table id="bkmrk-routing-key-directio"><thead><tr><th>Routing Key</th><th>Direction</th><th>Description</th></tr></thead><tbody><tr><td>`whatsapp.inbound.message`</td><td>WABA Gateway → Nexgate</td><td>Customer sent a message (text, image, any media)</td></tr><tr><td>`whatsapp.inbound.status`</td><td>WABA Gateway → Nexgate</td><td>Delivery/read status update for Nexgate's outbound messages</td></tr><tr><td>`whatsapp.outbound.message`</td><td>Nexgate → WABA Gateway</td><td>Send any message to customer (template or freetext, determined by `type` field)</td></tr><tr><td>`whatsapp.outbound.auth`</td><td>Nexgate → WABA Gateway</td><td>Send OTP to customer via WhatsApp (`nexgate_otp` template only)</td></tr></tbody></table>

> Status flows **one way only** — Meta → WABA Gateway → Nexgate. Nexgate never publishes status back.

---

## Inbound Events (WABA Gateway publishes, Nexgate consumes)

### Common Inbound Fields

<table id="bkmrk-field-type-required-"><thead><tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>`type`</td><td>`WabaEventType`</td><td>✅</td><td>`MESSAGE_RECEIVED` or `STATUS_WEBHOOK`</td></tr><tr><td>`messageType`</td><td>`WabaMessageType`</td><td>✅</td><td>`TEXT`, `IMAGE`, `DOCUMENT`, `AUDIO`, `VIDEO`</td></tr><tr><td>`wabaAccountId`</td><td>string</td><td>✅</td><td>WABA business account ID</td></tr><tr><td>`wabaNumber`</td><td>string</td><td>✅</td><td>Shop's WhatsApp number (E.164)</td></tr><tr><td>`wabaUserName`</td><td>string</td><td>✅</td><td>Customer's WhatsApp display name</td></tr><tr><td>`messageId`</td><td>string</td><td>✅</td><td>Meta message ID</td></tr><tr><td>`from`</td><td>string</td><td>✅</td><td>Customer's phone number (E.164)</td></tr><tr><td>`text`</td><td>string</td><td>⚠️</td><td>Message text (null for media messages)</td></tr><tr><td>`status`</td><td>string</td><td>⚠️</td><td>Delivery status (only for `STATUS_WEBHOOK`)</td></tr><tr><td>`media`</td><td>object</td><td>⚠️</td><td>Media payload (null for text messages)</td></tr><tr><td>`timestamp`</td><td>ISO-8601</td><td>✅</td><td>Event timestamp</td></tr></tbody></table>

### Media Object (inbound)

<table id="bkmrk-field-type-descripti"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>`type`</td><td>`WabaMessageType`</td><td>`IMAGE`, `DOCUMENT`, `AUDIO`, `VIDEO`</td></tr><tr><td>`url`</td><td>string</td><td>Media URL from Meta</td></tr><tr><td>`mimeType`</td><td>string</td><td>e.g. `image/jpeg`</td></tr><tr><td>`filename`</td><td>string</td><td>Original filename</td></tr><tr><td>`sha256`</td><td>string</td><td>Integrity checksum from Meta</td></tr><tr><td>`caption`</td><td>string</td><td>Optional caption from customer</td></tr></tbody></table>

---

### Sample: TEXT message received

**Routing key:** `whatsapp.inbound.message`

```json
{
  "type": "MESSAGE_RECEIVED",
  "messageType": "TEXT",
  "wabaAccountId": "waba-001",
  "wabaNumber": "+255700000000",
  "wabaUserName": "Josh",
  "messageId": "msg-123",
  "from": "+255712345678",
  "text": "Nataka kuku na chips",
  "media": null,
  "timestamp": "2026-04-12T11:00:00Z"
}

```

---

### Sample: IMAGE message received

**Routing key:** `whatsapp.inbound.message`

```json
{
  "type": "MESSAGE_RECEIVED",
  "messageType": "IMAGE",
  "wabaAccountId": "waba-001",
  "wabaNumber": "+255700000000",
  "wabaUserName": "Josh",
  "messageId": "msg-124",
  "from": "+255712345678",
  "text": null,
  "media": {
    "type": "IMAGE",
    "url": "https://example.com/image.jpg",
    "mimeType": "image/jpeg",
    "filename": "photo.jpg",
    "sha256": "abc123def456",
    "caption": "Hii ndiyo nataka"
  },
  "timestamp": "2026-04-12T11:01:00Z"
}

```

---

### Sample: STATUS update

**Routing key:** `whatsapp.inbound.status`

```json
{
  "type": "STATUS_WEBHOOK",
  "messageType": "TEXT",
  "wabaAccountId": "waba-001",
  "wabaNumber": "+255700000000",
  "wabaUserName": "Josh",
  "messageId": "msg-123",
  "from": "+255712345678",
  "status": "delivered",
  "timestamp": "2026-04-12T11:00:30Z"
}

```

> Possible `status` values: `sent`, `delivered`, `read`, `failed`

---

## Outbound Events (Nexgate publishes, WABA Gateway consumes)

### Common Outbound Fields

<table id="bkmrk-field-type-required--1"><thead><tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>`type`</td><td>`WabaEventType`</td><td>✅</td><td>`SEND_TEMPLATE`, `SEND_FREETEXT`, or `SEND_AUTH_OTP`</td></tr><tr><td>`messageType`</td><td>`WabaMessageType`</td><td>✅</td><td>`TEXT`, `TEMPLATE`, `IMAGE`, `DOCUMENT`, `AUDIO`, `VIDEO`</td></tr><tr><td>`wabaAccountId`</td><td>string</td><td>✅</td><td>WABA business account ID</td></tr><tr><td>`wabaNumber`</td><td>string</td><td>✅</td><td>WhatsApp number to send from (E.164)</td></tr><tr><td>`phoneNumberId`</td><td>string</td><td>⚠️</td><td>Meta phone number ID — present on `SEND_AUTH_OTP` events only, use directly without DB lookup</td></tr><tr><td>`appId`</td><td>string</td><td>⚠️</td><td>Meta app ID — present on `SEND_AUTH_OTP` events only</td></tr><tr><td>`to`</td><td>string</td><td>✅</td><td>Customer's phone number (E.164)</td></tr><tr><td>`correlationId`</td><td>string</td><td>⚠️</td><td>Order/booking/session ID for tracing</td></tr><tr><td>`text`</td><td>string</td><td>⚠️</td><td>Message text (for `SEND_FREETEXT` + `TEXT`)</td></tr><tr><td>`template`</td><td>object</td><td>⚠️</td><td>Template payload (for `SEND_TEMPLATE` and `SEND_AUTH_OTP`)</td></tr><tr><td>`media`</td><td>object</td><td>⚠️</td><td>Media payload (for image/document/audio/video)</td></tr></tbody></table>

> For `SEND_TEMPLATE` and `SEND_FREETEXT` (shop chatbot), `phoneNumberId` and `appId` will be `null` — look them up from DB via `wabaNumber` as normal. For `SEND_AUTH_OTP`, `phoneNumberId` and `appId` are always populated — use them directly, no DB lookup needed.

### Template Object (outbound)

<table id="bkmrk-field-type-descripti-1"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>`templateName`</td><td>`WabaTemplateName`</td><td>Enum — see reference table below</td></tr><tr><td>`languageCode`</td><td>string</td><td>e.g. `en`, `sw`</td></tr><tr><td>`params`</td><td>map</td><td>Named params mapped to Meta `{{1}}` `{{2}}` by WABA Gateway</td></tr></tbody></table>

### Media Object (outbound)

<table id="bkmrk-field-type-descripti-2"><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>`url`</td><td>string</td><td>Publicly accessible media URL</td></tr><tr><td>`mimeType`</td><td>string</td><td>e.g. `image/jpeg`</td></tr><tr><td>`filename`</td><td>string</td><td>Filename for documents</td></tr><tr><td>`caption`</td><td>string</td><td>Optional caption</td></tr></tbody></table>

---

### Sample: SEND\_FREETEXT (plain text)

**Routing key:** `whatsapp.outbound.message`

```json
{
  "type": "SEND_FREETEXT",
  "messageType": "TEXT",
  "wabaAccountId": "waba-001",
  "wabaNumber": "+255700000000",
  "to": "+255712345678",
  "correlationId": "session-001",
  "text": "Habari! Tunayo kuku na chips. Bei ni 5,000 TZS."
}

```

---

### Sample: SEND\_TEMPLATE — shop\_details

**Routing key:** `whatsapp.outbound.message`

```json
{
  "type": "SEND_TEMPLATE",
  "messageType": "TEMPLATE",
  "wabaAccountId": "waba-001",
  "wabaNumber": "+255700000000",
  "to": "+255712345678",
  "correlationId": "session-002",
  "template": {
    "templateName": "SHOP_DETAILS",
    "languageCode": "en",
    "params": {
      "1": "Josh",
      "2": "Mode Bites"
    }
  }
}

```

> Template body: `Hello {{1}}! Welcome to *{{2}}*, browse our products and find what suits you.`

---

### Sample: SEND\_TEMPLATE — product\_results\_found

**Routing key:** `whatsapp.outbound.message`

```json
{
  "type": "SEND_TEMPLATE",
  "messageType": "TEMPLATE",
  "wabaAccountId": "waba-001",
  "wabaNumber": "+255700000000",
  "to": "+255712345678",
  "correlationId": "session-003",
  "template": {
    "templateName": "PRODUCT_RESULTS_FOUND",
    "languageCode": "en",
    "params": {
      "1": "Josh",
      "2": "2",
      "3": "kuku",
      "4": "Mode Bites",
      "5": "Kuku Choma"
    }
  }
}

```

> Template body: `Hello {{1}}! 🎉 We found {{2}} products matching *"{{3}}"* in {{4}}. Here are a few matches: *{{5}}*. Tap below to see all results!`

---

### Sample: SEND\_TEMPLATE — products\_not\_found

**Routing key:** `whatsapp.outbound.message`

```json
{
  "type": "SEND_TEMPLATE",
  "messageType": "TEMPLATE",
  "wabaAccountId": "waba-001",
  "wabaNumber": "+255700000000",
  "to": "+255712345678",
  "correlationId": "session-004",
  "template": {
    "templateName": "PRODUCTS_NOT_FOUND",
    "languageCode": "en",
    "params": {
      "1": "Josh",
      "2": "cake",
      "3": "Mode Bites",
      "4": "Kuku Choma"
    }
  }
}

```

> Template body: `Hello {{1}}, We couldn't find *"{{2}}"* in *{{3}}* right now. You might be interested in: *{{4}}*`

---

### Sample: SEND\_TEMPLATE — fallback\_text

**Routing key:** `whatsapp.outbound.message`

```json
{
  "type": "SEND_TEMPLATE",
  "messageType": "TEMPLATE",
  "wabaAccountId": "waba-001",
  "wabaNumber": "+255700000000",
  "to": "+255712345678",
  "correlationId": "session-005",
  "template": {
    "templateName": "FALLBACK_TEXT",
    "languageCode": "en",
    "params": {
      "1": "Josh",
      "2": "sawa asante"
    }
  }
}

```

> Template body: `Hi {{1}}, thanks for your message: "{{2}}". We're not sure how to help with that — feel free to ask about our products or shop.`
> 
> **When is this sent?**
> 
> - AI is **enabled** but cannot classify the message as a product search or shop details request (e.g. greetings, thanks, unclear/off-topic messages)
> - AI is **disabled** — every inbound message defaults to this template, with the raw customer text passed as `{{2}}`

---

### Sample: SEND\_AUTH\_OTP

**Routing key:** `whatsapp.outbound.auth`

```json
{
  "type": "SEND_AUTH_OTP",
  "messageType": "TEMPLATE",
  "wabaAccountId": "nexgate-waba-account-id",
  "wabaNumber": "+255700000001",
  "phoneNumberId": "nexgate-phone-number-id",
  "appId": "nexgate-app-id",
  "to": "+255712345678",
  "correlationId": "+255712345678",
  "template": {
    "templateName": "NEXGATE_OTP",
    "languageCode": "en",
    "params": {
      "1": "847291"
    }
  }
}

```

> Template body: `847291 is your verification code. For your security, do not share this code. Expires in 10 minutes.`
> 
> This event always uses the **NextGate platform WABA account**, not a shop account. `phoneNumberId` and `appId` are always present — use them directly without any DB lookup.

---

### Sample: SEND\_FREETEXT (image)

**Routing key:** `whatsapp.outbound.message`

```json
{
  "type": "SEND_FREETEXT",
  "messageType": "IMAGE",
  "wabaAccountId": "waba-001",
  "wabaNumber": "+255700000000",
  "to": "+255712345678",
  "correlationId": "session-006",
  "media": {
    "url": "https://cdn.nexgate.co.tz/menu.jpg",
    "mimeType": "image/jpeg",
    "filename": "menu.jpg",
    "caption": "Menu ya leo"
  }
}

```

---

## Enums Reference

### WabaEventType

<table id="bkmrk-value-routing-key-di"><thead><tr><th>Value</th><th>Routing Key</th><th>Direction</th></tr></thead><tbody><tr><td>`MESSAGE_RECEIVED`</td><td>`whatsapp.inbound.message`</td><td>WABA Gateway → Nexgate</td></tr><tr><td>`STATUS_WEBHOOK`</td><td>`whatsapp.inbound.status`</td><td>WABA Gateway → Nexgate</td></tr><tr><td>`SEND_TEMPLATE`</td><td>`whatsapp.outbound.message`</td><td>Nexgate → WABA Gateway</td></tr><tr><td>`SEND_FREETEXT`</td><td>`whatsapp.outbound.message`</td><td>Nexgate → WABA Gateway</td></tr><tr><td>`SEND_AUTH_OTP`</td><td>`whatsapp.outbound.auth`</td><td>Nexgate → WABA Gateway</td></tr></tbody></table>

### WabaMessageType

<table id="bkmrk-value-description-te"><thead><tr><th>Value</th><th>Description</th></tr></thead><tbody><tr><td>`TEXT`</td><td>Plain text message</td></tr><tr><td>`TEMPLATE`</td><td>Meta approved template</td></tr><tr><td>`IMAGE`</td><td>Image (jpg, png)</td></tr><tr><td>`DOCUMENT`</td><td>PDF, docx etc</td></tr><tr><td>`AUDIO`</td><td>Voice/audio message</td></tr><tr><td>`VIDEO`</td><td>Video message</td></tr></tbody></table>

### WabaTemplateName

<table id="bkmrk-value-meta-template-"><thead><tr><th>Value</th><th>Meta Template Name</th><th>Variables</th></tr></thead><tbody><tr><td>`SHOP_DETAILS`</td><td>`shop_details`</td><td>`{{1}}` customerName, `{{2}}` shopName</td></tr><tr><td>`PRODUCTS_NOT_FOUND`</td><td>`products_not_found`</td><td>`{{1}}` customerName, `{{2}}` searchQuery, `{{3}}` shopName, `{{4}}` suggestedProducts</td></tr><tr><td>`PRODUCT_RESULTS_FOUND`</td><td>`product_results_found`</td><td>`{{1}}` customerName, `{{2}}` productCount, `{{3}}` searchQuery, `{{4}}` shopName, `{{5}}` topProducts</td></tr><tr><td>`FALLBACK_TEXT`</td><td>`fallback_text`</td><td>`{{1}}` customerName, `{{2}}` originalMessage</td></tr><tr><td>`NEXGATE_OTP`</td><td>`nexgate_otp`</td><td>`{{1}}` otp code</td></tr></tbody></table>

---

## Notes

- All phone numbers must be in **E.164 format** e.g. `+255712345678`
- `correlationId` should be the session/order/booking ID for end-to-end tracing
- Free text messages (`SEND_FREETEXT`) are only valid within the **24-hour session window** after customer last messaged
- Template messages (`SEND_TEMPLATE` and `SEND_AUTH_OTP`) can be sent anytime
- `wabaUserName` is the customer's WhatsApp display name — pass it to personalize template greetings
- `SEND_AUTH_OTP` events arrive on `q.whatsapp.auth` — handle them separately from shop messages on `q.whatsapp.outbound`
- For `SEND_AUTH_OTP`, never attempt a DB lookup for `phoneNumberId` or `appId` — they are always present in the event