From 13dedaf48df0caa4f075c840969a18a2d7dc7248 Mon Sep 17 00:00:00 2001 From: Claude AI Date: Thu, 29 Jan 2026 22:43:19 +0000 Subject: [PATCH] feat(odoo): add WhatsApp webhook controller Co-Authored-By: Claude Opus 4.5 --- odoo_whatsapp_hub/controllers/webhook.py | 140 +++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 odoo_whatsapp_hub/controllers/webhook.py diff --git a/odoo_whatsapp_hub/controllers/webhook.py b/odoo_whatsapp_hub/controllers/webhook.py new file mode 100644 index 0000000..26c6981 --- /dev/null +++ b/odoo_whatsapp_hub/controllers/webhook.py @@ -0,0 +1,140 @@ +from odoo import http +from odoo.http import request +import json +import logging + +_logger = logging.getLogger(__name__) + + +class WhatsAppWebhookController(http.Controller): + + @http.route('/whatsapp/webhook', type='json', auth='public', methods=['POST'], csrf=False) + def webhook(self, **kwargs): + """ + Receive webhooks from WhatsApp Central. + Events: message, status_update, conversation_update + """ + try: + data = request.jsonrequest + event_type = data.get('type') + + _logger.info(f'WhatsApp webhook received: {event_type}') + + handlers = { + 'message': self._handle_message, + 'status_update': self._handle_status_update, + 'conversation_update': self._handle_conversation_update, + 'account_status': self._handle_account_status, + } + + handler = handlers.get(event_type) + if handler: + return handler(data) + + return {'status': 'ignored', 'reason': f'Unknown event type: {event_type}'} + + except Exception as e: + _logger.error(f'WhatsApp webhook error: {e}') + return {'status': 'error', 'message': str(e)} + + def _handle_message(self, data): + """Handle incoming message""" + msg_data = data.get('data', {}) + account_external_id = data.get('account_id') + conversation_external_id = msg_data.get('conversation_id') + + account = request.env['whatsapp.account'].sudo().search([ + ('external_id', '=', account_external_id) + ], limit=1) + + if not account: + return {'status': 'ignored', 'reason': 'Account not found'} + + conversation = request.env['whatsapp.conversation'].sudo().search([ + ('external_id', '=', conversation_external_id) + ], limit=1) + + if not conversation: + phone = msg_data.get('from', '').split('@')[0] + conversation = request.env['whatsapp.conversation'].sudo().create({ + 'external_id': conversation_external_id, + 'account_id': account.id, + 'phone_number': phone, + 'contact_name': msg_data.get('contact_name'), + 'status': 'bot', + }) + + partner = request.env['res.partner'].sudo().search([ + '|', + ('phone', 'ilike', phone[-10:]), + ('mobile', 'ilike', phone[-10:]), + ], limit=1) + + if partner: + conversation.partner_id = partner + + request.env['whatsapp.message'].sudo().create({ + 'external_id': msg_data.get('id'), + 'conversation_id': conversation.id, + 'direction': 'inbound', + 'message_type': msg_data.get('type', 'text'), + 'content': msg_data.get('content'), + 'media_url': msg_data.get('media_url'), + 'status': 'delivered', + }) + + return {'status': 'ok'} + + def _handle_status_update(self, data): + """Handle message status update""" + msg_data = data.get('data', {}) + external_id = msg_data.get('message_id') + new_status = msg_data.get('status') + + message = request.env['whatsapp.message'].sudo().search([ + ('external_id', '=', external_id) + ], limit=1) + + if message: + message.write({'status': new_status}) + + return {'status': 'ok'} + + def _handle_conversation_update(self, data): + """Handle conversation status update""" + conv_data = data.get('data', {}) + external_id = conv_data.get('conversation_id') + new_status = conv_data.get('status') + + conversation = request.env['whatsapp.conversation'].sudo().search([ + ('external_id', '=', external_id) + ], limit=1) + + if conversation: + conversation.write({'status': new_status}) + + return {'status': 'ok'} + + def _handle_account_status(self, data): + """Handle account status change""" + acc_data = data.get('data', {}) + external_id = data.get('account_id') + new_status = acc_data.get('status') + + account = request.env['whatsapp.account'].sudo().search([ + ('external_id', '=', external_id) + ], limit=1) + + if account: + account.write({ + 'status': new_status, + 'phone_number': acc_data.get('phone_number'), + 'qr_code': acc_data.get('qr_code'), + }) + + return {'status': 'ok'} + + @http.route('/whatsapp/webhook/test', type='http', auth='public', methods=['GET']) + def webhook_test(self): + """Test endpoint to verify webhook connectivity""" + return json.dumps({'status': 'ok', 'message': 'WhatsApp webhook is active'})