AI Knowledge & Logic

Event Handlers

Run custom Python code automatically when conversation events occur — close, handoff, and more. Use event handlers for CRM updates, notifications, analytics, and post-conversation workflows.

Event handlers let you run Python code automatically when specific events happen in your conversations. Unlike custom actions (which the AI calls during a conversation), event handlers trigger after key moments — when a conversation is closed, when it's handed off to a human, and more in the future.

Common uses:

  • Update your CRM when a conversation closes
  • Send a Slack notification when a conversation is handed off
  • Log conversation data to an external analytics system
  • Trigger a follow-up email or survey after resolution

Supported events

EventTrigger
Conversation ClosedFires when a conversation is closed (by agent, bot, or automation)
Conversation Handed OffFires when the bot hands off a conversation to a human agent

More events will be added over time. Each event handler is configured for exactly one event type.


How it works

  1. You create an event handler — give it a name, select the event type, and write your Python code
  2. When the event occurs on any conversation in your organization, the handler runs automatically
  3. The handler receives full conversation context — messages, metadata, and the event type
  4. Results are logged for debugging and monitoring

You can create multiple handlers for the same event. They all run independently.


Writing an event handler

An event handler implements a handle_event function. It receives the same context object as custom actions.

import requests

def handle_event(context):
    event_type = context["args"]["event_type"]
    conversation = context["conversation"]

    # Post to your webhook
    requests.post("https://api.example.com/webhook", json={
        "event": event_type,
        "conversationId": conversation["publicId"],
        "subject": conversation["subject"],
        "messageCount": len(conversation["messages"]),
    }, timeout=30)

    return {"status": "notified"}

Context structure

The context dictionary contains:

FieldDescription
argsDictionary with event_type key (e.g., "conversation_closed")
conversationFull conversation object (always present for event handlers)
browser_sessionBrowser session data (only for web chat conversations)

The conversation object includes:

FieldDescription
idConversation UUID
publicIdShort public ID (e.g., "ABC123XYZ")
subjectConversation subject line
businessSlugBusiness identifier
messagesArray of message objects

Each message has sender ("customer", "bot", or "agent"), timestamp (ISO 8601), content (text), and channel.


Available helper functions

Event handlers have access to all the same helper functions as custom actions:

  • add_conversation_tag(context, tag) — Add a tag to the conversation
  • set_conversation_metadata(context, key, value) — Store metadata on the conversation
  • get_conversation_metadata(context, key) — Retrieve stored metadata
  • delete_conversation_metadata(context, key) — Remove metadata
  • add_conversation_event(context, event) — Create a conversation event
  • llm_classify_binary(prompt, input, fallback=None) — Binary AI classification
  • llm_classify_category(prompt, input, options, fallback=None) — Category AI classification
  • add_google_sheets_row(sheet_id, data) — Append a row to Google Sheets
  • is_holiday(country, state=None, region=None, check_date=None) — Check if a date is a holiday

Call list_predefined_functions() in your code to see all available helpers at runtime.


Example: CRM update on conversation close

Update your CRM with conversation summary data when a conversation is resolved.

import requests

def handle_event(context):
    conversation = context["conversation"]
    messages = conversation["messages"]

    customer_messages = [m for m in messages if m["sender"] == "customer"]

    requests.post("https://api.example.com/crm/conversations", json={
        "externalId": conversation["publicId"],
        "subject": conversation["subject"],
        "messageCount": len(messages),
        "customerMessageCount": len(customer_messages),
        "resolvedAt": messages[-1]["timestamp"] if messages else None,
    }, timeout=30)

    return {"status": "synced"}

Example: Slack notification on handoff

Alert your team when a conversation needs human attention.

import requests

def handle_event(context):
    conversation = context["conversation"]
    messages = conversation["messages"]

    # Get last few customer messages for context
    recent = [m["content"] for m in messages if m["sender"] == "customer"][-3:]

    requests.post("https://hooks.slack.com/services/YOUR/WEBHOOK/URL", json={
        "text": f"Conversation {conversation['publicId']} handed off",
        "blocks": [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"*Handoff:* {conversation['subject']}\n*ID:* {conversation['publicId']}\n*Recent messages:*\n" + "\n".join(f"> {m}" for m in recent)
                }
            }
        ]
    }, timeout=10)

    return {"status": "notified"}

Example: Google Sheets logging

Log every closed conversation to a Google Sheet for reporting.

def handle_event(context):
    conversation = context["conversation"]
    messages = conversation["messages"]

    add_google_sheets_row("YOUR_SPREADSHEET_ID", {
        "Conversation ID": conversation["publicId"],
        "Subject": conversation["subject"],
        "Messages": len(messages),
        "Closed At": messages[-1]["timestamp"] if messages else "",
        "Business": conversation["businessSlug"],
    })

    return {"status": "logged"}

Testing

Event handlers can be tested from the dashboard before they go live.

  1. Open your event handler in the dashboard
  2. Enter a Conversation ID — this loads the real conversation data as context. Use any conversation ID or public ID from your dashboard
  3. Click Run Test
  4. The dashboard executes your code and shows the result, stdout, and stderr

The test simulates the event without actually triggering it. The selected event type from the form is passed as context["args"]["event_type"].

Tip: Test with conversations that represent different scenarios — short conversations, long ones, ones with file attachments — to make sure your handler is robust.


Best practices

  • Keep handlers fast. Event handlers run asynchronously but have a 60-second timeout. Avoid heavy processing — offload to external systems if needed.
  • Handle errors gracefully. If your external API is down, catch the exception and return an error status rather than crashing. Failed executions are logged for debugging.
  • Use metadata for state. If you need to track whether a handler has already processed a conversation, use set_conversation_metadata / get_conversation_metadata to avoid duplicate processing.
  • Be idempotent. Events may occasionally trigger more than once. Design your handlers so running them twice on the same conversation produces the same result.
  • Check the event type. If you plan to reuse similar logic across events, check context["args"]["event_type"] to branch behavior.

On this page