listener.py— connects to the Autoplay stream, writes raw actions and LLM-generated summaries toevent_data.txt.webhook.py— receives Crisp messages, readsevent_data.txtas context, queries an LLM, and sends the reply back as an operator message.
event_data.txt (near real-time):
End-to-end walkthrough
🔗 Step 1 — Create a Crisp AI account
In Crisp, select the Crisp AI option when setting up your inbox.Note: Keep the Hugo AI option disabled — it conflicts with the custom webhook bot you’ll set up in Step 2.
⚙️ Step 2 — Configure the webhook and plugin
In your Crisp dashboard, go to Settings → Integrations → Webhooks and register your webhook endpoint (you’ll get the URL in Step 3). Make sure the Plugin tier is enabled for your account — it’s required to send operator messages via the API.🖥️ Step 3 — Set up the webhook server
This FastAPI app receives Crisp messages, reads the event context written by the listener, and replies via the Crisp REST API.Project structure
Prerequisites
- Python 3.10+
- An OpenAI API key
uvicornfor serving the FastAPI app- (Optional) Cloudflare Tunnel or Ngrok for local development
Configuration
Replace the placeholder values below with your actual credentials. Never commit real secrets to version control — use environment variables or a secrets manager in production.Configuration — webhook constants (stream, Crisp API, webhook secret)
Configuration — webhook constants (stream, Crisp API, webhook secret)
Security tip: Load these from environment variables usingos.getenv()orpython-dotenvbefore deploying.
Code node: send_crisp_message
This helper posts a text message into a Crisp conversation on behalf of an operator (your bot).
Code: send_crisp_message — post operator reply via Crisp REST
Code: send_crisp_message — post operator reply via Crisp REST
- Crisp’s REST API uses HTTP Basic Auth with your token identifier and key Base64-encoded.
- The
X-Crisp-Tier: pluginheader is required for plugin-tier integrations. from: "operator"ensures the message appears as a bot/agent reply, not a visitor message.
Code node: get_event_data_from_file
The support AI agent is grounded in data from a local text file. Swap this out with a database query, vector search, or API call depending on your use case.
Code: get_event_data_from_file — read shared context file
Code: get_event_data_from_file — read shared context file
Code node: llm
This function builds a prompt that injects the event data as context, then calls the OpenAI chat completions API.
Code: llm() — system prompt, activity context, and OpenAI chat completion
Code: llm() — system prompt, activity context, and OpenAI chat completion
- The system prompt teaches the model when and how to use the activity data — not just that it exists.
- The user-turn context is labelled
## Current User Activityso the model treats it as a named record, not raw noise. max_tokensis raised to512to give the model room for numbered steps and follow-up suggestions.temperature=0.3keeps responses factual. Raise it slightly (e.g.0.5) if you want warmer, more conversational replies.
Code node: handle_message
Crisp enforces a strict 2-second timeout on webhook delivery. This wrapper lets the HTTP response return immediately while the AI call runs in the background.
Code: handle_message — background task for LLM + Crisp reply
Code: handle_message — background task for LLM + Crisp reply
FastAPI webhook endpoint
The main webhook listens for POST requests from Crisp and dispatches the background task.Code: FastAPI app — /crisp/hooks webhook and health route
Code: FastAPI app — /crisp/hooks webhook and health route
- The webhook secret is passed as a query parameter (
?key=...) and verified on every request. - Filtering for
from == "user"is critical — without it, the bot would respond to its own messages, creating an infinite loop. asyncio.create_task()schedules the AI work without blocking the response.- Crisp will retry delivery if it doesn’t receive
200 OK, so the finalreturn {"status": "ok"}must always be reached.
Running locally
Commands: run webhook locally (uvicorn, tunnel, public URL)
Commands: run webhook locally (uvicorn, tunnel, public URL)
Complete code — webhook.py
View complete webhook.py
View complete webhook.py
🐍 Step 4 — Set up the real-time event listener
This Python script connects to your Autoplay stream, writes structured action data toevent_data.txt, and periodically appends an LLM-generated summary.
Prerequisites
- Python 3.10+
- An Autoplay SDK account with a stream URL and API key
- An OpenAI API key
Project structure
Configuration
Configuration — listener stream URL, API key, and Crisp credentials
Configuration — listener stream URL, API key, and Crisp credentials
HTTP clients, decorator, and summarizer LLM
Two async clients are initialised at module level: one for Crisp (available for extension), one for OpenAI, plus theapi_call decorator and the small llm helper used by the summarizer.
Code: HTTP clients, api_call decorator, and summarizer LLM helper
Code: HTTP clients, api_call decorator, and summarizer LLM helper
AsyncSessionSummarizer to generate session summaries:httpx.AsyncClient and openai.AsyncOpenAI.
Apply @api_call to any async function that makes a network or I/O call. This keeps the main event loop alive even when individual calls fail.
temperature=0.3 keeps summaries factual and deterministic. Increase it if you want more descriptive prose.
Write actions to file
Each incoming batch of actions is appended toevent_data.txt. The file is reset if it hasn’t been modified in 2 minutes, which serves as a simple heuristic for detecting a new user session.
Code: write_actions_to_file — append structured actions to event_data.txt
Code: write_actions_to_file — append structured actions to event_data.txt
Note: The 2-minute reset is intentionally simple. For production, consider keying the file by session_id or using a proper store (Redis, SQLite) to handle concurrent users.
Summary writer callbacks
AsyncAgentContextWriter expects two callbacks: one to write actions (which we skip, handling that ourselves above), and one to persist the generated summary.
Code: summary callbacks and AsyncAgentContextWriter (summarizer + threshold)
Code: summary callbacks and AsyncAgentContextWriter (summarizer + threshold)
=== SUMMARY === header so any consumer (like the Crisp support AI agent) can distinguish raw action logs from synthesised summaries.
threshold=5 means the summarizer fires after every 5 actions. Lower it for faster summaries; raise it to reduce LLM calls. debounce_ms=0 disables write debouncing — safe here since writes are cheap file appends.
Action interceptor
The central handler called by the SDK for each incoming action batch. It does two things in sequence: writes raw structured data to file, then forwards the payload to the agent writer for summarisation tracking.Code: handle_actions_interceptor and AsyncConnectorClient main()
Code: handle_actions_interceptor and AsyncConnectorClient main()
async with context manager ensures the connection is cleanly closed on exit or interruption.
Running the listener
Command: run the listener process
Command: run the listener process
systemd, supervisord, or a process manager like pm2 to ensure it restarts on failure.
Complete code — listener.py
View complete listener.py
View complete listener.py
🌐 Step 5 — Add the support AI agent to your frontend
Copy the Crisp embed snippet from your Crisp dashboard and paste it into your frontendindex.html file, just before the closing </body> tag.
Code: Crisp chat widget embed snippet (index.html)
Code: Crisp chat widget embed snippet (index.html)
That’s it. Events will start appearing in
event_data.txt as soon as the user interacts with your UI. Summaries are generated and appended automatically once the action threshold is reached. The Crisp support AI agent will use all of this data to answer visitor questions with real-time context.
Next: Step 2 — Define proactive triggers