# Task Team Connector — Inbound webhook controller import hashlib import hmac import logging from odoo import http from odoo.http import request _logger = logging.getLogger(__name__) class TaskTeamWebhookController(http.Controller): @http.route( '/task_team/webhook/sync', type='json', auth='public', methods=['POST'], csrf=False, ) def sync_webhook(self, **kwargs): """Receive task-sync events from Task Team API.""" ICP = request.env['ir.config_parameter'].sudo() secret = ICP.get_param('task_team_connector.webhook_secret', '') # HMAC-SHA256 signature verification (when secret is configured) if secret: signature = request.httprequest.headers.get('X-Webhook-Signature', '') body = request.httprequest.get_data() expected = 'sha256=' + hmac.new( secret.encode(), body, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(expected, signature): _logger.warning('Task Team webhook: invalid HMAC signature') return {'error': 'invalid_signature', 'code': 401} data = request.get_json_data() event = data.get('event', 'task_updated') _logger.info('Task Team inbound webhook: event=%s', event) if event in ('task_created', 'task_updated'): task_data = data.get('task') or data request.env['project.task'].sudo().tt_upsert_from_webhook(task_data) return {'status': 'ok', 'event': event} if event == 'task_deleted': tt_id = data.get('id') or data.get('tt_task_id') if tt_id: task = request.env['project.task'].sudo().search( [('tt_task_id', '=', tt_id)], limit=1 ) if task: task.with_context(tt_no_sync=True).write({'active': False}) return {'status': 'ok', 'event': event} if event == 'task_completed': # Mark the Odoo task stage as closed if possible tt_id = data.get('tt_task_id') if tt_id: task = request.env['project.task'].sudo().search( [('tt_task_id', '=', tt_id)], limit=1 ) if task and task.project_id: closed_stage = request.env['project.task.type'].sudo().search( [ ('project_ids', 'in', task.project_id.id), ('is_closed', '=', True), ], limit=1, ) if closed_stage: task.with_context(tt_no_sync=True).write( {'stage_id': closed_stage.id} ) return {'status': 'ok', 'event': event} _logger.debug('Task Team webhook: unhandled event type %s', event) return {'status': 'ok', 'event': event, 'note': 'unhandled'} @http.route('/task_team/webhook/health', type='json', auth='public', methods=['GET']) def health(self, **kwargs): return {'status': 'ok', 'module': 'task_team_connector'}