97 lines
3.4 KiB
Python
97 lines
3.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
|
|
import json
|
|
import logging
|
|
|
|
from odoo import fields, http
|
|
from odoo.http import request, Response
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class RcWebhookController(http.Controller):
|
|
|
|
@http.route('/ringcentral/webhook', type='json', auth='none', csrf=False, methods=['POST'])
|
|
def webhook_handler(self, **kw):
|
|
"""Receive webhook events from RingCentral."""
|
|
headers = request.httprequest.headers
|
|
validation_token = headers.get('Validation-Token')
|
|
if validation_token:
|
|
return Response(
|
|
status=200,
|
|
headers={'Validation-Token': validation_token},
|
|
)
|
|
|
|
try:
|
|
body = json.loads(request.httprequest.get_data(as_text=True))
|
|
except (json.JSONDecodeError, TypeError):
|
|
_logger.warning("RingCentral webhook: invalid JSON body")
|
|
return {'status': 'error', 'message': 'Invalid JSON'}
|
|
|
|
event = body.get('event', '')
|
|
_logger.info("RingCentral webhook event: %s", event)
|
|
|
|
if 'telephony/sessions' in event:
|
|
self._handle_telephony_event(body)
|
|
|
|
return {'status': 'ok'}
|
|
|
|
def _handle_telephony_event(self, body):
|
|
"""Process a telephony session event."""
|
|
try:
|
|
event_body = body.get('body', {})
|
|
parties = event_body.get('parties', [])
|
|
|
|
for party in parties:
|
|
status_code = party.get('status', {}).get('code', '')
|
|
if status_code != 'Disconnected':
|
|
continue
|
|
|
|
direction_raw = party.get('direction', '').lower()
|
|
direction = 'inbound' if direction_raw == 'inbound' else 'outbound'
|
|
|
|
from_info = party.get('from', {})
|
|
to_info = party.get('to', {})
|
|
from_number = from_info.get('phoneNumber', '')
|
|
to_number = to_info.get('phoneNumber', '')
|
|
|
|
session_id = event_body.get('sessionId', '')
|
|
if not session_id:
|
|
continue
|
|
|
|
env = request.env(su=True)
|
|
existing = env['rc.call.history'].search_count([
|
|
('rc_session_id', '=', str(session_id)),
|
|
])
|
|
if existing:
|
|
continue
|
|
|
|
duration = party.get('duration', 0)
|
|
result = party.get('status', {}).get('reason', 'Unknown')
|
|
status_map = {
|
|
'Answered': 'answered',
|
|
'CallConnected': 'answered',
|
|
'HangUp': 'answered',
|
|
'Voicemail': 'voicemail',
|
|
'Missed': 'missed',
|
|
'NoAnswer': 'no_answer',
|
|
'Busy': 'busy',
|
|
'Rejected': 'rejected',
|
|
}
|
|
status = status_map.get(result, 'answered' if duration > 0 else 'missed')
|
|
|
|
env['rc.call.history'].create({
|
|
'rc_session_id': str(session_id),
|
|
'direction': direction,
|
|
'from_number': from_number,
|
|
'to_number': to_number,
|
|
'start_time': fields.Datetime.now(),
|
|
'duration': duration,
|
|
'status': status,
|
|
})
|
|
|
|
except Exception:
|
|
_logger.exception("Error processing telephony webhook event")
|