TechnoCore Automate — Internal Documentation
Handles WhatsApp conversation flow via the Channel360 messaging API.
ObjConversationWhatsapp.py contains the WhatsAppConversationHandler class, which acts as the bridge between TechnoCore's internal conversation engine (ObjConversation) and the Channel360 WhatsApp API.
It follows a stateless request/response pattern: each incoming WhatsApp message is processed, the conversation state is loaded from the database, a response is formatted, and the reply is sent back to the user — all within a single call.
| Import | Purpose |
|---|---|
ObjConversation |
Core conversation engine — handles state, DB read/write, and page logic |
ObjData |
Database access layer for SQL queries |
Objects |
Global config access (Objects.global_config) |
requests |
HTTP client used to call the Channel360 API |
re |
Regex used for emoji placeholder replacement |
The handler reads its Channel360 credentials from the global config file (config.ini) under the [channel360] section:
[channel360]
organisationid = YOUR_ORG_ID
apikey = YOUR_API_KEY
These are loaded in __init__ and used to authenticate every outbound API call.
The file defines a module-level _EMOJI_MAP dictionary that maps plain-text names to Unicode emoji characters. Two placeholder formats are supported in conversation text:
| Format | Example | Result |
|---|---|---|
{emoji:name} |
{emoji:star} |
⭐ |
{name} |
{star} |
⭐ |
Unknown placeholders (e.g. {report:xyz}) are left untouched, so non-emoji template variables pass through safely.
The regex _EMOJI_RE and the _replace_emoji() method handle the substitution. This is applied to all user-facing text before it is sent.
WhatsAppConversationHandler__init__(self)Initialises the handler by:
organisationid and apikey from the global configObjConversation instance for conversation managementget_conversation_code(self, app_id)Looks up the conversation code associated with a given app_id (the WhatsApp app/connection identifier) by querying def_conversation.
conversation_code = handler.get_conversation_code("my_app_id")
Returns a lowercase string conversation code used to load the correct conversation flow.
process_message(self, phone_id, text, app_id)The main entry point. Call this for every incoming WhatsApp message.
Arguments:
| Parameter | Type | Description |
|---|---|---|
phone_id |
str |
The user's WhatsApp phone number |
text |
str |
The message text or button reply ID sent by the user |
app_id |
str |
Identifier for the WhatsApp app/connection |
Flow:
conversation_code from app_idconv.read_conversation() to load state, process input, and advance the conversationconv.get_pagetext(pageno) to retrieve the page options (opts)opts to _format_whatsapp_message() to build the API payloadsend_whatsapp_message() to deliver the responseReturns:
# Success
{"success": True, "message": <formatted_dict>}
# Failure
{"success": False, "error": "<exception message>", "message": "Sorry, I encountered an error..."}
_format_whatsapp_message(self, opts)Converts the raw opts dictionary from the conversation engine into a correctly structured Channel360 / WhatsApp API message payload.
opts Keys Used| Key | Description |
|---|---|
header |
Main body text shown in the message |
chatheader |
Secondary header text (bold in plain text, header block in interactive) |
headerimage |
URL of an image to use as the message header |
media_url |
URL of a file/document to send |
option1 – option9 |
Interactive button options (dict with command and show) |
optionh |
"Help" footer option |
optionr |
"Restart" footer option |
The method selects the message format based on what data is present:
1. Interactive (buttons present)
Used when at least one option1–option9 entry is valid. WhatsApp supports a maximum of 3 buttons.
{
"type": "interactive",
"interactive": {
"type": "button",
"header": { ... },
"body": { "text": "..." },
"footer": { "text": "..." },
"action": {
"buttons": [
{ "type": "reply", "reply": { "id": "001", "title": "Option Text" } }
]
}
}
}
Header priority (first match wins):
headerimage → image headermedia_url → document headerchatheader → text headerWhatsApp limits that are enforced in code:
| Element | Limit |
|---|---|
| Button title | 20 characters |
| Header text | 60 characters |
| Footer text | 60 characters |
| Buttons per message | 3 |
2. Image (no buttons, headerimage set)
{
"type": "image",
"image": {
"link": "https://example.com/image.jpg",
"caption": "Optional caption text"
}
}
3. Document (no buttons, media_url set)
{
"type": "document",
"document": {
"link": "https://example.com/file.pdf",
"filename": "file.pdf",
"caption": "Optional caption text"
}
}
The filename is extracted automatically from the last segment of the URL.
4. Plain Text (fallback)
{
"type": "text",
"text": {
"body": "*Header*\n\nBody text\n\nHelp | Restart"
}
}
chatheader is bolded using WhatsApp's *bold* markdown syntax.
send_whatsapp_message(self, text, phone_id)Posts the formatted message payload to the Channel360 API.
Endpoint:
POST https://www.channel360.co.za/v1.1/org/{org_id}/whatsapp/appuser/{phone_id}/reply
Headers:
Authorization: Bearer <api_key>
Content-Type: application/json
The full payload dict is sent as the JSON body. The raw response object from requests is returned.
opts received
│
├── buttons found (option1–option9)?
│ └── YES → Interactive message
│ Header: image > document > text
│
└── NO buttons
├── headerimage set? → Image message
├── media_url set? → Document message
└── fallback → Plain text message
opts Dictionaryopts = {
"header": "Please choose a service:",
"chatheader": "Welcome to Support",
"media_url": "https://example.com/brochure.pdf",
"option1": {"command": "1", "show": "Billing"},
"option2": {"command": "2", "show": "Technical Support"},
"option3": {"command": "3", "show": "Account Info"},
"optionh": {"show": "Help"},
"optionr": {"show": "Restart"},
}
This would produce an interactive message with 3 buttons, a document header (the brochure PDF), body text "Please choose a service:", and a footer of "Help | Restart".