webhook integration
Browse files- app.py +14 -14
- components/gateways/headlines_to_wa.py +7 -65
- routes/api/whatsapp_webhook.py +114 -0
app.py
CHANGED
|
@@ -1,34 +1,34 @@
|
|
| 1 |
-
# app.py
|
| 2 |
import os
|
| 3 |
import sys
|
| 4 |
from fastapi import FastAPI
|
|
|
|
|
|
|
| 5 |
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
|
| 6 |
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "components")))
|
| 7 |
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "routes")))
|
| 8 |
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "routes", "api")))
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
#
|
| 12 |
-
from routes.api import ingest # Assuming routes/api/ingest.py exists and has a 'router'
|
| 13 |
-
from routes.api import query # Assuming routes/api/query.py exists and has a 'router'
|
| 14 |
-
from routes.api import headlines # Assuming routes/api/headlines.py exists and has a 'router'
|
| 15 |
-
from routes.api import wa_headlines
|
| 16 |
-
|
| 17 |
-
# You included Settings in your original, so I'll put it back.
|
| 18 |
-
# NOTE: Settings.llm = None can cause issues if LlamaIndex operations
|
| 19 |
-
# elsewhere expect a global LLM to be configured via Settings.
|
| 20 |
from llama_index.core.settings import Settings
|
| 21 |
Settings.llm = None
|
| 22 |
|
| 23 |
-
|
| 24 |
app = FastAPI()
|
| 25 |
|
| 26 |
@app.get("/")
|
| 27 |
def greet():
|
| 28 |
return {"welcome": "nuse ai"}
|
| 29 |
|
| 30 |
-
#
|
| 31 |
app.include_router(ingest.router)
|
| 32 |
app.include_router(query.router)
|
| 33 |
app.include_router(headlines.router)
|
| 34 |
-
app.include_router(wa_headlines.router)
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import sys
|
| 3 |
from fastapi import FastAPI
|
| 4 |
+
|
| 5 |
+
# Add paths to sys.path to allow relative imports
|
| 6 |
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
|
| 7 |
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "components")))
|
| 8 |
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "routes")))
|
| 9 |
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "routes", "api")))
|
| 10 |
|
| 11 |
+
# Import your API routes
|
| 12 |
+
from routes.api import ingest # routes/api/ingest.py
|
| 13 |
+
from routes.api import query # routes/api/query.py
|
| 14 |
+
from routes.api import headlines # routes/api/headlines.py
|
| 15 |
+
from routes.api import wa_headlines # routes/api/wa_headlines.py
|
| 16 |
+
from routes.api import whatsapp_webhook as whatsapp_webhook_router_module
|
| 17 |
|
| 18 |
+
# Optional: Global Settings (LlamaIndex)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
from llama_index.core.settings import Settings
|
| 20 |
Settings.llm = None
|
| 21 |
|
| 22 |
+
# Create FastAPI app
|
| 23 |
app = FastAPI()
|
| 24 |
|
| 25 |
@app.get("/")
|
| 26 |
def greet():
|
| 27 |
return {"welcome": "nuse ai"}
|
| 28 |
|
| 29 |
+
# Include your route modules
|
| 30 |
app.include_router(ingest.router)
|
| 31 |
app.include_router(query.router)
|
| 32 |
app.include_router(headlines.router)
|
| 33 |
+
app.include_router(wa_headlines.router)
|
| 34 |
+
app.include_router(whatsapp_webhook_router_module.router, prefix="/api/whatsapp", tags=["WhatsApp Webhook"])
|
components/gateways/headlines_to_wa.py
CHANGED
|
@@ -2,8 +2,8 @@ import os
|
|
| 2 |
import json
|
| 3 |
import redis
|
| 4 |
import requests
|
| 5 |
-
from fastapi import FastAPI
|
| 6 |
-
from fastapi.responses import JSONResponse
|
| 7 |
import logging
|
| 8 |
|
| 9 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
@@ -11,15 +11,12 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(
|
|
| 11 |
# 🌐 Configuration from Environment Variables
|
| 12 |
# These variables MUST be set in your environment (e.g., .env file, shell exports, deployment configs)
|
| 13 |
REDIS_URL = os.environ.get("UPSTASH_REDIS_URL", "redis://localhost:6379")
|
| 14 |
-
# Reverting API URL to generic WhatsApp message endpoint
|
| 15 |
WHATSAPP_API_URL = os.environ.get("WHATSAPP_API_URL", "https://api.gupshup.io/wa/api/v1/msg")
|
| 16 |
WHATSAPP_TOKEN = os.environ.get("WHATSAPP_TOKEN")
|
| 17 |
WHATSAPP_TO_NUMBER = os.environ.get("WHATSAPP_TO_NUMBER", "353899495777") # e.g., "91xxxxxxxxxx"
|
| 18 |
GUPSHUP_SOURCE_NUMBER = os.environ.get("GUPSHUP_SOURCE_NUMBER") # e.g., your WABA number
|
| 19 |
GUPSHUP_APP_NAME = os.environ.get("GUPSHUP_APP_NAME") # e.g., your Gupshup app name
|
| 20 |
|
| 21 |
-
# Removed: WHATSAPP_CATALOG_ID, WHATSAPP_PRODUCT_RETAILER_ID, WHATSAPP_FOOTER_TEXT, WHATSAPP_TEMPLATE_ID
|
| 22 |
-
|
| 23 |
# ✅ Redis connection
|
| 24 |
try:
|
| 25 |
redis_client = redis.Redis.from_url(REDIS_URL, decode_responses=True)
|
|
@@ -30,7 +27,6 @@ except Exception as e:
|
|
| 30 |
raise
|
| 31 |
|
| 32 |
# 🧾 Fetch and format headlines
|
| 33 |
-
# Reverting this function to generate the full text message content
|
| 34 |
def fetch_cached_headlines() -> str:
|
| 35 |
try:
|
| 36 |
raw = redis_client.get("detailed_news_feed_cache")
|
|
@@ -64,7 +60,6 @@ def fetch_cached_headlines() -> str:
|
|
| 64 |
return "\n".join(message_parts)
|
| 65 |
|
| 66 |
# 📤 Send via Gupshup WhatsApp API
|
| 67 |
-
# Reverting to send a standard text message
|
| 68 |
def send_to_whatsapp(message_text: str) -> dict: # Function expects the full message text
|
| 69 |
# Validate critical environment variables for sending a message
|
| 70 |
if not WHATSAPP_TOKEN or \
|
|
@@ -81,7 +76,6 @@ def send_to_whatsapp(message_text: str) -> dict: # Function expects the full mes
|
|
| 81 |
"Cache-Control": "no-cache" # Add Cache-Control header
|
| 82 |
}
|
| 83 |
|
| 84 |
-
# <<< REVERTED MESSAGE PAYLOAD FOR STANDARD TEXT MESSAGE >>>
|
| 85 |
whatsapp_message_content = {
|
| 86 |
"type": "text",
|
| 87 |
"text": message_text # This is the full formatted text
|
|
@@ -112,61 +106,9 @@ def send_to_whatsapp(message_text: str) -> dict: # Function expects the full mes
|
|
| 112 |
logging.error(f"❌ An unexpected error occurred during WhatsApp send: {e}")
|
| 113 |
return {"status": "failed", "error": str(e), "code": 500}
|
| 114 |
|
| 115 |
-
#
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
@app.get("/send-daily-whatsapp")
|
| 119 |
-
def send_daily_whatsapp_digest():
|
| 120 |
-
logging.info("API Call: /send-daily-whatsapp initiated.")
|
| 121 |
-
|
| 122 |
-
# fetch_cached_headlines now returns the full message text
|
| 123 |
-
full_message_text = fetch_cached_headlines()
|
| 124 |
-
|
| 125 |
-
if full_message_text.startswith("❌") or full_message_text.startswith("⚠️"):
|
| 126 |
-
logging.warning(f"Returning error due to issue fetching headlines: {full_message_text}")
|
| 127 |
-
return JSONResponse(status_code=404, content={"error": full_message_text})
|
| 128 |
-
|
| 129 |
-
# Call send_to_whatsapp with the full formatted text
|
| 130 |
-
result = send_to_whatsapp(full_message_text)
|
| 131 |
-
|
| 132 |
-
if result.get("status") == "success":
|
| 133 |
-
logging.info("✅ WhatsApp message sent successfully.")
|
| 134 |
-
return JSONResponse(status_code=200, content=result)
|
| 135 |
-
else:
|
| 136 |
-
logging.error(f"❌ Failed to send WhatsApp message: {result.get('error')}")
|
| 137 |
-
return JSONResponse(status_code=result.get("code", 500), content=result)
|
| 138 |
|
| 139 |
-
#
|
| 140 |
-
|
| 141 |
-
# For local testing, ensure these environment variables are set in your shell or .env file.
|
| 142 |
-
# Example .env content:
|
| 143 |
-
# UPSTASH_REDIS_URL="redis://your_redis_url"
|
| 144 |
-
# WHATSAPP_API_URL="https://api.gupshup.io/wa/api/v1/msg"
|
| 145 |
-
# WHATSAPP_TOKEN="YOUR_GUPSHUP_API_KEY"
|
| 146 |
-
# WHATSAPP_TO_NUMBER="919999999999"
|
| 147 |
-
# GUPSHUP_SOURCE_NUMBER="15557926439"
|
| 148 |
-
# GUPSHUP_APP_NAME="NuseAI"
|
| 149 |
-
|
| 150 |
-
# Simulate a cached detailed feed for testing
|
| 151 |
-
dummy_cached_detailed_feed = {
|
| 152 |
-
"india": {
|
| 153 |
-
"1": {"title": "India's Economy Surges", "description": "Rapid growth in manufacturing and services sectors signals a strong economic recovery, boosting investor confidence and job creation.", "sources": ["Times of India"]},
|
| 154 |
-
"2": {"title": "New Tech Policy Unveiled", "description": "Government introduces new regulations to foster innovation while addressing data privacy concerns, aiming to balance growth with user protection.", "sources": ["Indian Express"]}
|
| 155 |
-
},
|
| 156 |
-
"world": {
|
| 157 |
-
"3": {"title": "Global Climate Talks Advance", "description": "Nations agree on ambitious new targets for emissions reduction, marking a significant step towards combating climate change despite earlier disagreements.", "sources": ["BBC News"]},
|
| 158 |
-
"4": {"title": "Space Mission Explores Mars", "description": "A new rover successfully lands on Mars, sending back groundbreaking data that could revolutionize our understanding of planetary geology and potential for life.", "sources": ["CNN World"]}
|
| 159 |
-
}
|
| 160 |
-
}
|
| 161 |
-
# Store dummy data in Redis for testing fetch_cached_headlines
|
| 162 |
-
redis_client.set("detailed_news_feed_cache", json.dumps(dummy_cached_detailed_feed))
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
logging.info("\n--- WhatsApp Message Preview ---\n")
|
| 166 |
-
msg_preview = fetch_cached_headlines()
|
| 167 |
-
print(msg_preview)
|
| 168 |
-
|
| 169 |
-
logging.info("\n--- Sending WhatsApp Message (Test) ---\n")
|
| 170 |
-
# This will attempt to send a real message if your env vars are valid
|
| 171 |
-
test_result = send_to_whatsapp(msg_preview)
|
| 172 |
-
print(test_result)
|
|
|
|
| 2 |
import json
|
| 3 |
import redis
|
| 4 |
import requests
|
| 5 |
+
from fastapi import FastAPI # This import is not strictly needed in this file if it's just a module
|
| 6 |
+
from fastapi.responses import JSONResponse # This import is not strictly needed in this file if it's just a module
|
| 7 |
import logging
|
| 8 |
|
| 9 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
| 11 |
# 🌐 Configuration from Environment Variables
|
| 12 |
# These variables MUST be set in your environment (e.g., .env file, shell exports, deployment configs)
|
| 13 |
REDIS_URL = os.environ.get("UPSTASH_REDIS_URL", "redis://localhost:6379")
|
|
|
|
| 14 |
WHATSAPP_API_URL = os.environ.get("WHATSAPP_API_URL", "https://api.gupshup.io/wa/api/v1/msg")
|
| 15 |
WHATSAPP_TOKEN = os.environ.get("WHATSAPP_TOKEN")
|
| 16 |
WHATSAPP_TO_NUMBER = os.environ.get("WHATSAPP_TO_NUMBER", "353899495777") # e.g., "91xxxxxxxxxx"
|
| 17 |
GUPSHUP_SOURCE_NUMBER = os.environ.get("GUPSHUP_SOURCE_NUMBER") # e.g., your WABA number
|
| 18 |
GUPSHUP_APP_NAME = os.environ.get("GUPSHUP_APP_NAME") # e.g., your Gupshup app name
|
| 19 |
|
|
|
|
|
|
|
| 20 |
# ✅ Redis connection
|
| 21 |
try:
|
| 22 |
redis_client = redis.Redis.from_url(REDIS_URL, decode_responses=True)
|
|
|
|
| 27 |
raise
|
| 28 |
|
| 29 |
# 🧾 Fetch and format headlines
|
|
|
|
| 30 |
def fetch_cached_headlines() -> str:
|
| 31 |
try:
|
| 32 |
raw = redis_client.get("detailed_news_feed_cache")
|
|
|
|
| 60 |
return "\n".join(message_parts)
|
| 61 |
|
| 62 |
# 📤 Send via Gupshup WhatsApp API
|
|
|
|
| 63 |
def send_to_whatsapp(message_text: str) -> dict: # Function expects the full message text
|
| 64 |
# Validate critical environment variables for sending a message
|
| 65 |
if not WHATSAPP_TOKEN or \
|
|
|
|
| 76 |
"Cache-Control": "no-cache" # Add Cache-Control header
|
| 77 |
}
|
| 78 |
|
|
|
|
| 79 |
whatsapp_message_content = {
|
| 80 |
"type": "text",
|
| 81 |
"text": message_text # This is the full formatted text
|
|
|
|
| 106 |
logging.error(f"❌ An unexpected error occurred during WhatsApp send: {e}")
|
| 107 |
return {"status": "failed", "error": str(e), "code": 500}
|
| 108 |
|
| 109 |
+
# Removed the FastAPI app instance and endpoint from here,
|
| 110 |
+
# as it will be defined in routes/api/wa_headlines.py
|
| 111 |
+
# and then included in app.py.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
+
# Removed the if __name__ == "__main__": block from here,
|
| 114 |
+
# as it will be in routes/api/wa_headlines.py for local testing.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
routes/api/whatsapp_webhook.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# routes/api/whatsapp_webhook.py
|
| 2 |
+
from fastapi import APIRouter, Request, HTTPException, status
|
| 3 |
+
from fastapi.responses import JSONResponse
|
| 4 |
+
import logging
|
| 5 |
+
import json
|
| 6 |
+
|
| 7 |
+
# Import your function to send messages back
|
| 8 |
+
from components.gateways.headlines_to_wa import fetch_cached_headlines, send_to_whatsapp
|
| 9 |
+
|
| 10 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 11 |
+
|
| 12 |
+
router = APIRouter()
|
| 13 |
+
|
| 14 |
+
# WhatsApp/Gupshup webhook endpoint
|
| 15 |
+
@router.post("/message-received")
|
| 16 |
+
async def whatsapp_webhook_receiver(request: Request):
|
| 17 |
+
"""
|
| 18 |
+
Receives incoming messages from Gupshup WhatsApp webhook.
|
| 19 |
+
Sends a daily news digest if the user sends a specific command.
|
| 20 |
+
"""
|
| 21 |
+
try:
|
| 22 |
+
# Gupshup sends data as application/x-www-form-urlencoded
|
| 23 |
+
# or sometimes as raw JSON depending on setup.
|
| 24 |
+
# We need to try parsing both.
|
| 25 |
+
try:
|
| 26 |
+
form_data = await request.form()
|
| 27 |
+
payload_str = form_data.get('payload') # Gupshup often wraps JSON in a 'payload' field
|
| 28 |
+
if payload_str:
|
| 29 |
+
incoming_message = json.loads(payload_str)
|
| 30 |
+
else: # If not 'payload' field, try direct form parsing
|
| 31 |
+
incoming_message = dict(form_data)
|
| 32 |
+
except json.JSONDecodeError:
|
| 33 |
+
# Fallback for raw JSON body (less common for Gupshup, but good to have)
|
| 34 |
+
incoming_message = await request.json()
|
| 35 |
+
except Exception as e:
|
| 36 |
+
logging.error(f"Error parsing webhook request body: {e}")
|
| 37 |
+
return JSONResponse(status_code=400, content={"status": "error", "message": "Invalid request format"})
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
logging.info(f"Received WhatsApp webhook: {json.dumps(incoming_message, indent=2)}")
|
| 41 |
+
|
| 42 |
+
# Extract relevant info (Gupshup webhook structure can vary, common fields used below)
|
| 43 |
+
# This part might need fine-tuning based on actual Gupshup webhook JSON
|
| 44 |
+
message_data = incoming_message.get('payload', {}).get('payload', {}) # Gupshup often double-nests 'payload'
|
| 45 |
+
|
| 46 |
+
# Try a different path if the above didn't work (common for raw JSON webhooks)
|
| 47 |
+
if not message_data:
|
| 48 |
+
message_data = incoming_message.get('message', {})
|
| 49 |
+
if not message_data: # Sometimes the direct message object is at the top level
|
| 50 |
+
message_data = incoming_message
|
| 51 |
+
|
| 52 |
+
from_number = message_data.get('sender', {}).get('phone') or message_data.get('from')
|
| 53 |
+
message_text = message_data.get('message', {}).get('text') or message_data.get('body') # Common text fields
|
| 54 |
+
|
| 55 |
+
if not from_number or not message_text:
|
| 56 |
+
logging.warning("Received webhook without valid sender or message text.")
|
| 57 |
+
return JSONResponse(status_code=200, content={"status": "ignored", "message": "Missing sender or text"})
|
| 58 |
+
|
| 59 |
+
logging.info(f"Message from {from_number}: {message_text}")
|
| 60 |
+
|
| 61 |
+
# Check for specific commands to send the digest
|
| 62 |
+
if message_text.lower().strip() == "digest":
|
| 63 |
+
logging.info(f"User {from_number} requested daily digest.")
|
| 64 |
+
|
| 65 |
+
# Fetch the digest headlines
|
| 66 |
+
full_message_text = fetch_cached_headlines()
|
| 67 |
+
|
| 68 |
+
if full_message_text.startswith("❌") or full_message_text.startswith("⚠️"):
|
| 69 |
+
logging.error(f"Failed to fetch digest for {from_number}: {full_message_text}")
|
| 70 |
+
# Send an error message back to the user
|
| 71 |
+
send_to_whatsapp(f"Sorry, I couldn't fetch the news digest today. {full_message_text}", destination_number=from_number)
|
| 72 |
+
return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to fetch digest"})
|
| 73 |
+
|
| 74 |
+
# Send the digest back to the user who requested it
|
| 75 |
+
result = send_to_whatsapp(full_message_text, destination_number=from_number)
|
| 76 |
+
|
| 77 |
+
if result.get("status") == "success":
|
| 78 |
+
logging.info(f"✅ Successfully sent digest to {from_number}.")
|
| 79 |
+
return JSONResponse(status_code=200, content={"status": "success", "message": "Digest sent"})
|
| 80 |
+
else:
|
| 81 |
+
logging.error(f"❌ Failed to send digest to {from_number}: {result.get('error')}")
|
| 82 |
+
# Send an error message back to the user
|
| 83 |
+
send_to_whatsapp(f"Sorry, I couldn't send the news digest to you. Error: {result.get('error', 'unknown')}", destination_number=from_number)
|
| 84 |
+
return JSONResponse(status_code=500, content={"status": "error", "message": "Failed to send digest"})
|
| 85 |
+
else:
|
| 86 |
+
logging.info(f"Received unhandled message from {from_number}: '{message_text}'")
|
| 87 |
+
# Optional: Send a generic response for unhandled commands
|
| 88 |
+
# send_to_whatsapp("Sorry, I only understand 'Digest' for now.", destination_number=from_number)
|
| 89 |
+
return JSONResponse(status_code=200, content={"status": "ignored", "message": "No action taken for this command"})
|
| 90 |
+
|
| 91 |
+
except Exception as e:
|
| 92 |
+
logging.error(f"Error processing webhook: {e}", exc_info=True)
|
| 93 |
+
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
|
| 94 |
+
|
| 95 |
+
# Gupshup webhook verification endpoint (GET request with 'hub.mode' and 'hub.challenge')
|
| 96 |
+
@router.get("/message-received")
|
| 97 |
+
async def whatsapp_webhook_verify(request: Request):
|
| 98 |
+
"""
|
| 99 |
+
Endpoint for Gupshup webhook verification.
|
| 100 |
+
"""
|
| 101 |
+
mode = request.query_params.get("hub.mode")
|
| 102 |
+
challenge = request.query_params.get("hub.challenge")
|
| 103 |
+
verify_token = request.query_params.get("hub.verify_token") # You might set an env var for this
|
| 104 |
+
|
| 105 |
+
# Gupshup typically doesn't require a verify_token, unlike Facebook directly.
|
| 106 |
+
# However, if you configure it, you should check it.
|
| 107 |
+
# For now, we'll just return the challenge if mode is 'subscribe'.
|
| 108 |
+
|
| 109 |
+
if mode == "subscribe" and challenge:
|
| 110 |
+
logging.info(f"Webhook verification successful. Challenge: {challenge}")
|
| 111 |
+
return JSONResponse(status_code=200, content=int(challenge)) # Challenge needs to be an integer
|
| 112 |
+
else:
|
| 113 |
+
logging.warning(f"Webhook verification failed. Mode: {mode}, Challenge: {challenge}")
|
| 114 |
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Verification failed")
|