File size: 2,176 Bytes
214209a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import logging

from fastapi import APIRouter, Request, Response, HTTPException
from twilio.request_validator import RequestValidator
from twilio.twiml.messaging_response import MessagingResponse
from ..config import settings
from ..services.ingest import handle_incoming_message

router = APIRouter()
logger = logging.getLogger(__name__)


def _form_data_to_payload(form_data):
    payload = {}
    for key, value in form_data.multi_items():
        if key in payload:
            existing_value = payload[key]
            if isinstance(existing_value, list):
                existing_value.append(value)
            else:
                payload[key] = [existing_value, value]
        else:
            payload[key] = value
    return payload


def _validate_twilio_signature(request: Request, form_data):
    if not settings.TWILIO_VALIDATE_SIGNATURES:
        return

    signature = request.headers.get("X-Twilio-Signature")
    if not signature:
        raise HTTPException(status_code=403, detail="Missing Twilio signature")

    validator = RequestValidator(settings.TWILIO_AUTH_TOKEN)
    payload = _form_data_to_payload(form_data)
    request_url = str(request.url)
    is_valid = validator.validate(request_url, payload, signature)
    if not is_valid:
        raise HTTPException(status_code=403, detail="Invalid Twilio signature")


async def _handle_twilio_webhook(request: Request, channel: str) -> Response:
    form_data = await request.form()
    _validate_twilio_signature(request, form_data)
    response_message = await handle_incoming_message(form_data, channel=channel)

    twiml = MessagingResponse()
    if response_message:
        twiml.message(response_message)

    logger.info("Returning %s TwiML response for channel=%s", "non-empty" if response_message else "empty", channel)
    return Response(content=str(twiml), media_type="application/xml")

@router.post("/twilio/whatsapp")
async def twilio_whatsapp_webhook(request: Request):
    return await _handle_twilio_webhook(request, channel="whatsapp")

@router.post("/twilio/sms")
async def twilio_sms_webhook(request: Request):
    return await _handle_twilio_webhook(request, channel="sms")