diff --git a/funnel/views/api/__init__.py b/funnel/views/api/__init__.py index ebadd6087..60df96480 100644 --- a/funnel/views/api/__init__.py +++ b/funnel/views/api/__init__.py @@ -12,4 +12,5 @@ shortlink, sms_events, support, + whatsapp_events, ) diff --git a/funnel/views/api/whatsapp_events.py b/funnel/views/api/whatsapp_events.py index 6b7a85512..c39335e61 100644 --- a/funnel/views/api/whatsapp_events.py +++ b/funnel/views/api/whatsapp_events.py @@ -2,7 +2,7 @@ from __future__ import annotations -from flask import current_app, request +from flask import current_app, jsonify, request from baseframe import statsd @@ -12,13 +12,34 @@ from ...utils import abort_null +@app.route('/api/1/whatsapp/meta_event', methods=['GET']) +def process_whatsapp_webhook_verification(): + """Meta requires to verify the webhook URL by sending a GET request with a token.""" + verify_token = app.config['WHATSAPP_WEBHOOK_VERIFY_CODE'] + mode = request.args.get("hub.mode") + token = request.args.get("hub.verify_token") + challenge = request.args.get("hub.challenge") + + if mode and token: + if mode == "subscribe" and token == verify_token: + return challenge, 200 + return "Forbidden", 403 + return "Success", 200 + + @app.route('/api/1/whatsapp/meta_event', methods=['POST']) def process_whatsapp_event() -> ReturnView: """Process WhatsApp callback event.""" # Register the fact that we got a WhatsApp event. # If there are too many rejects, then most likely a hack attempt. statsd.incr('phone_number.event', tags={'engine': 'whatsapp', 'stage': 'received'}) - whatsapp_to = abort_null(request.form.get('display_phone_number', '')) + whatsapp_to = abort_null( + request.json.get("entry", [{}])[0] + .get("changes", [{}])[0] + .get("value", {}) + .get("statuses", [{}])[0] + .get("recipient_id") + ) if not whatsapp_to: return {'status': 'eror', 'error': 'invalid_phone'}, 422 # Exotel sends back 0-prefixed phone numbers, not plus-prefixed intl. numbers @@ -26,6 +47,8 @@ def process_whatsapp_event() -> ReturnView: whatsapp_to = '+' + whatsapp_to[2:] elif whatsapp_to.startswith('0'): whatsapp_to = '+91' + whatsapp_to[1:] + elif whatsapp_to.startswith('91'): + whatsapp_to = '+' + whatsapp_to try: whatsapp_to = canonical_phone_number(whatsapp_to) except PhoneNumberError: @@ -33,14 +56,26 @@ def process_whatsapp_event() -> ReturnView: whatsapp_message = PhoneNumber.query.filter_by(number=whatsapp_to).one_or_none() - if request.form['status'] == 'delivered': + status = ( + request.json.get("entry", [{}])[0] + .get("changes", [{}])[0] + .get("value", {}) + .get("statuses", [{}])[0] + .get("status") + ) + + if status == 'sent': + whatsapp_message.msg_wa_sent_at = sa.func.utcnow() + if status == 'delivered': whatsapp_message.msg_wa_delivered_at = sa.func.utcnow() + if status == 'failed': + whatsapp_message.msg_wa_failed_at = sa.func.utcnow() db.session.commit() current_app.logger.info( "WhatsApp event for phone: %s %s", whatsapp_to, - request.form['status'], + status, ) statsd.incr( @@ -48,7 +83,7 @@ def process_whatsapp_event() -> ReturnView: tags={ 'engine': 'whatsapp', 'stage': 'processed', - 'event': request.form['status'], + 'event': status, }, ) - return {'status': 'ok', 'message': 'whatsapp_notification_processed'} + return jsonify({"status": "ok"}), 200