Skip to content

Application Routes

loglife.core.routes.webhook.routes

Webhook endpoint for inbound WhatsApp messages.

Receives POST requests, validates payloads, and enqueues messages for processing.

webhook()

Handle inbound WhatsApp messages.

Source code in src/loglife/core/routes/webhook/routes.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@webhook_bp.route("/webhook", methods=["POST"])
def webhook() -> ResponseReturnValue:
    """Handle inbound WhatsApp messages."""
    try:
        data: dict = request.get_json()

        message = Message.from_payload(data)
        g.client_type = message.client_type  # expose client type to sender service

        enqueue_inbound_message(message)

        logger.info("Queued message type %s for %s", message.msg_type, message.sender)
        # For emulator, we don't need an explicit "queued" response message in the UI
        # We return an empty message so the emulator doesn't show "Message queued"
        return success_response(message="")
    except Exception as e:
        error = f"Error processing webhook > {e}"
        logger.exception(error)
        return error_response(error)

whatsapp_incoming()

Handle incoming messages from Meta WhatsApp Cloud API.

GET: Webhook verification (Meta sends verification challenge) POST: Processes text and interactive list messages, creates a custom payload, and forwards to /webhook. List selections are sent as text commands.

Source code in src/loglife/core/routes/webhook/routes.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
@webhook_bp.route("/whatsapp-incoming", methods=["GET", "POST"])
def whatsapp_incoming() -> ResponseReturnValue:
    """Handle incoming messages from Meta WhatsApp Cloud API.

    GET: Webhook verification (Meta sends verification challenge)
    POST: Processes text and interactive list messages, creates a custom payload,
          and forwards to /webhook. List selections are sent as text commands.
    """
    if request.method == "GET":
        return _handle_webhook_verification()

    # POST method - handle incoming messages
    try:
        data: dict = request.get_json()
        return _process_meta_message(data)
    except Exception:
        logger.exception("Error processing Meta webhook")
        return error_response("Error processing Meta webhook")

loglife.core.routes.emulator.routes

Web-based emulator for testing chat flows.

Serves the emulator UI and provides an SSE stream for realtime logs.

emulator()

Render the emulator HTML interface.

Source code in src/loglife/core/routes/emulator/routes.py
28
29
30
31
@emulator_bp.route("/")
def emulator() -> str:
    """Render the emulator HTML interface."""
    return render_template("emulator.html", db_url=EMULATOR_SQLITE_WEB_URL)

events()

Stream realtime log events to the browser via SSE.

Source code in src/loglife/core/routes/emulator/routes.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
@emulator_bp.route("/events")
def events() -> Response:
    """Stream realtime log events to the browser via SSE."""

    def stream() -> Generator[str, None, None]:
        # Listen yields messages from the broadcaster
        for msg in log_broadcaster.listen():
            # Handle multiline messages for SSE
            formatted_msg = msg.replace("\n", "\ndata: ")
            yield f"data: {formatted_msg}\n\n"

    response = Response(stream(), mimetype="text/event-stream")
    response.headers["Cache-Control"] = "no-cache"
    response.headers["X-Accel-Buffering"] = "no"
    return response

vapi_admin()

Render the VAPI assistant admin panel.

Source code in src/loglife/core/routes/emulator/routes.py
34
35
36
37
38
39
40
41
42
43
44
@emulator_bp.route("/vapi-admin")
def vapi_admin() -> str:
    """Render the VAPI assistant admin panel."""
    # Get assistant IDs from environment
    assistant_ids = {
        "1": os.getenv("NEXT_PUBLIC_VAPI_ASSISTANT_ID_1", ""),
        "2": os.getenv("NEXT_PUBLIC_VAPI_ASSISTANT_ID_2", ""),
        "3": os.getenv("NEXT_PUBLIC_VAPI_ASSISTANT_ID_3", ""),
        "4": os.getenv("NEXT_PUBLIC_VAPI_ASSISTANT_ID_4", ""),
    }
    return render_template("vapi-admin.html", assistant_ids=assistant_ids)

vapi_assistant(assistant_id)

Fetch or update assistant configuration from VAPI API.

Source code in src/loglife/core/routes/emulator/routes.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@emulator_bp.route("/vapi-admin/api/assistant/<assistant_id>", methods=["GET", "PATCH"])
def vapi_assistant(assistant_id: str) -> Response:
    """Fetch or update assistant configuration from VAPI API."""
    vapi_private_key = os.getenv("VAPI_PRIVATE_KEY")

    if not vapi_private_key:
        return jsonify({"error": "VAPI_PRIVATE_KEY is not configured"}), 500

    if request.method == "GET":
        return _fetch_assistant(assistant_id, vapi_private_key)

    if request.method == "PATCH":
        return _update_assistant(assistant_id, vapi_private_key)

    return jsonify({"error": "Method not allowed"}), 405