# File: src/bland_webhook.py # Purpose: FastAPI webhook endpoint that receives Bland AI call events and runs the pipeline from fastapi import FastAPI, Request, HTTPException from pathlib import Path import json import sys sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from src.pipeline import run_pipeline from src.stt import transcribe_from_bland_webhook app = FastAPI(title="Bland AI Webhook Receiver", version="1.0") @app.post("/webhook/bland") async def bland_webhook(request: Request): """ Receive a Bland AI call webhook. Bland AI sends a POST with: - call_id: unique call identifier - transcript: full call transcript (if available) - recording_url: URL of audio recording - status: call status (e.g., "completed", "in-progress") This endpoint: 1. Extracts the transcript (from text or audio) 2. Runs the full verification pipeline 3. Returns the verified spoken response to Bland AI """ try: payload = await request.json() except Exception: raise HTTPException(status_code=400, detail="Invalid JSON payload") call_id = payload.get("call_id", "unknown") status = payload.get("status", "") print(f"[Webhook] Received call {call_id} with status: {status}") if status != "completed": return {"status": "ignored", "reason": "Call not yet completed"} # Get transcript from Bland AI (try text first, then audio transcription) transcript = payload.get("transcript", "").strip() if not transcript: recording_url = payload.get("recording_url", "") if recording_url: print(f"[Webhook] Transcribing from recording_url: {recording_url}") transcript = transcribe_from_bland_webhook(recording_url) if not transcript: return {"status": "error", "reason": "No transcript or audio available"} # Run the anti-hallucination pipeline result = run_pipeline(transcript) # Return the verified response to be spoken back to the patient return { "call_id": call_id, "response": result["response"], "escalated": result["escalated"], "appointment_id": ( result["verification"].appointment_id if result["verification"] and result["verification"].verified else None ), } @app.get("/health") def health(): return {"status": "ok"} if __name__ == "__main__": try: uvicorn = __import__("uvicorn") except ImportError: raise RuntimeError("uvicorn must be installed to run this server") from None uvicorn.run("src.bland_webhook:app", host="0.0.0.0", port=9000, reload=True)