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 & 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 status values: 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_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) 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. phoneNumberId and appId are 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 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