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 consumesoutbound= Nexgate publishes → WABA Gateway consumes
Exchange & Queues
| Name | Type | Purpose |
|---|---|---|
whatsapp.exchange |
topic | Single shared exchange |
q.whatsapp.inbound |
durable | Messages coming IN to Nexgate |
q.whatsapp.outbound |
durable | Messages going OUT from Nexgate (shop chatbot) |
q.whatsapp.auth |
durable | OTP delivery for registration and login |
Routing Keys
| Routing Key | Direction | Description |
|---|---|---|
whatsapp.inbound.message |
WABA Gateway → Nexgate | Customer sent a message (text, image, any media) |
whatsapp.inbound.status |
WABA Gateway → Nexgate | Delivery/read status update for Nexgate's outbound messages |
whatsapp.outbound.message |
Nexgate → WABA Gateway | Send any message to customer (template or freetext, determined by type field) |
whatsapp.outbound.auth |
Nexgate → WABA Gateway | Send OTP to customer via WhatsApp (nexgate_otp template only) |
Status flows one way only — Meta → WABA Gateway → Nexgate. Nexgate never publishes status back.
Inbound Events (WABA Gateway publishes, Nexgate consumes)
Common Inbound Fields
| Field | Type | Required | Description |
|---|---|---|---|
type |
WabaEventType |
✅ | MESSAGE_RECEIVED or STATUS_WEBHOOK |
messageType |
WabaMessageType |
✅ | TEXT, IMAGE, DOCUMENT, AUDIO, VIDEO |
wabaAccountId |
string | ✅ | WABA business account ID |
wabaNumber |
string | ✅ | Shop's WhatsApp number (E.164) |
wabaUserName |
string | ✅ | Customer's WhatsApp display name |
messageId |
string | ✅ | Meta message ID |
from |
string | ✅ | Customer's phone number (E.164) |
text |
string | ⚠️ | Message text (null for media messages) |
status |
string | ⚠️ | Delivery status (only for STATUS_WEBHOOK) |
media |
object | ⚠️ | Media payload (null for text messages) |
timestamp |
ISO-8601 | ✅ | Event timestamp |
Media Object (inbound)
| Field | Type | Description |
|---|---|---|
type |
WabaMessageType |
IMAGE, DOCUMENT, AUDIO, VIDEO |
url |
string | Media URL from Meta |
mimeType |
string | e.g. image/jpeg |
filename |
string | Original filename |
sha256 |
string | Integrity checksum from Meta |
caption |
string | Optional caption from customer |
Sample: TEXT message received
Routing key: whatsapp.inbound.message
{
"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
{
"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
{
"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
statusvalues:sent,delivered,read,failed
Outbound Events (Nexgate publishes, WABA Gateway consumes)
Common Outbound Fields
| Field | Type | Required | Description |
|---|---|---|---|
type |
WabaEventType |
✅ | SEND_TEMPLATE, SEND_FREETEXT, or SEND_AUTH_OTP |
messageType |
WabaMessageType |
✅ | TEXT, TEMPLATE, IMAGE, DOCUMENT, AUDIO, VIDEO |
wabaAccountId |
string | ✅ | WABA business account ID |
wabaNumber |
string | ✅ | WhatsApp number to send from (E.164) |
phoneNumberId |
string | ⚠️ | Meta phone number ID — present on SEND_AUTH_OTP events only, use directly without DB lookup |
appId |
string | ⚠️ | Meta app ID — present on SEND_AUTH_OTP events only |
to |
string | ✅ | Customer's phone number (E.164) |
correlationId |
string | ⚠️ | Order/booking/session ID for tracing |
text |
string | ⚠️ | Message text (for SEND_FREETEXT + TEXT) |
template |
object | ⚠️ | Template payload (for SEND_TEMPLATE and SEND_AUTH_OTP) |
media |
object | ⚠️ | Media payload (for image/document/audio/video) |
For
SEND_TEMPLATEandSEND_FREETEXT(shop chatbot),phoneNumberIdandappIdwill benull— look them up from DB viawabaNumberas normal. ForSEND_AUTH_OTP,phoneNumberIdandappIdare always populated — use them directly, no DB lookup needed.
Template Object (outbound)
| Field | Type | Description |
|---|---|---|
templateName |
WabaTemplateName |
Enum — see reference table below |
languageCode |
string | e.g. en, sw |
params |
map | Named params mapped to Meta {{1}} {{2}} by WABA Gateway |
Media Object (outbound)
| Field | Type | Description |
|---|---|---|
url |
string | Publicly accessible media URL |
mimeType |
string | e.g. image/jpeg |
filename |
string | Filename for documents |
caption |
string | Optional caption |
Sample: SEND_FREETEXT (plain text)
Routing key: whatsapp.outbound.message
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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.
phoneNumberIdandappIdare always present — use them directly without any DB lookup.
Sample: SEND_FREETEXT (image)
Routing key: whatsapp.outbound.message
{
"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
| Value | Routing Key | Direction |
|---|---|---|
MESSAGE_RECEIVED |
whatsapp.inbound.message |
WABA Gateway → Nexgate |
STATUS_WEBHOOK |
whatsapp.inbound.status |
WABA Gateway → Nexgate |
SEND_TEMPLATE |
whatsapp.outbound.message |
Nexgate → WABA Gateway |
SEND_FREETEXT |
whatsapp.outbound.message |
Nexgate → WABA Gateway |
SEND_AUTH_OTP |
whatsapp.outbound.auth |
Nexgate → WABA Gateway |
WabaMessageType
| Value | Description |
|---|---|
TEXT |
Plain text message |
TEMPLATE |
Meta approved template |
IMAGE |
Image (jpg, png) |
DOCUMENT |
PDF, docx etc |
AUDIO |
Voice/audio message |
VIDEO |
Video message |
WabaTemplateName
| Value | Meta Template Name | Variables |
|---|---|---|
SHOP_DETAILS |
shop_details |
{{1}} customerName, {{2}} shopName |
PRODUCTS_NOT_FOUND |
products_not_found |
{{1}} customerName, {{2}} searchQuery, {{3}} shopName, {{4}} suggestedProducts |
PRODUCT_RESULTS_FOUND |
product_results_found |
{{1}} customerName, {{2}} productCount, {{3}} searchQuery, {{4}} shopName, {{5}} topProducts |
FALLBACK_TEXT |
fallback_text |
{{1}} customerName, {{2}} originalMessage |
NEXGATE_OTP |
nexgate_otp |
{{1}} otp code |
Notes
- All phone numbers must be in E.164 format e.g.
+255712345678 correlationIdshould 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_TEMPLATEandSEND_AUTH_OTP) can be sent anytime wabaUserNameis the customer's WhatsApp display name — pass it to personalize template greetingsSEND_AUTH_OTPevents arrive onq.whatsapp.auth— handle them separately from shop messages onq.whatsapp.outbound- For
SEND_AUTH_OTP, never attempt a DB lookup forphoneNumberIdorappId— they are always present in the event