Skip to main content
The EventHandler class provides callback methods for all Daily call events. Create a subclass and override the methods you want to handle.

Overview

The EventHandler receives notifications about call state changes, participants, media events, and more. Pass your EventHandler instance to the CallClient constructor.
from daily import EventHandler, CallClient

class MyEventHandler(EventHandler):
    def on_participant_joined(self, participant):
        print(f"Participant joined: {participant['info']['userName']}")
    
    def on_call_state_updated(self, state):
        print(f"Call state: {state}")

client = CallClient(event_handler=MyEventHandler())

Constructor

__init__()

Creates a new EventHandler instance.
handler = MyEventHandler()

Returns

A new EventHandler instance.

Call State Events

on_call_state_updated()

Called when the call state changes.
def on_call_state_updated(self, state: str) -> None:
    print(f"Call state: {state}")
    # States: "initialized", "joining", "joined", "left", "error"

Parameters

state
str
required
The new call state. Possible values:
  • "initialized" - Client is initialized
  • "joining" - Joining the call
  • "joined" - Successfully joined
  • "left" - Left the call
  • "error" - Error occurred

on_error()

Called when a call error occurs.
def on_error(self, message: str) -> None:
    print(f"Call error: {message}")

Parameters

message
str
required
Error message describing what went wrong.

Participant Events

on_participant_joined()

Called when a participant joins the call.
def on_participant_joined(self, participant: Mapping[str, Any]) -> None:
    print(f"Joined: {participant['info']['userName']}")
    print(f"ID: {participant['id']}")

Parameters

participant
Mapping[str, Any]
required
Dictionary containing participant information:
  • id - Participant ID
  • info - User info (userName, etc.)
  • media - Media state information
  • permissions - Participant permissions

on_participant_updated()

Called when participant information changes.
def on_participant_updated(self, participant: Mapping[str, Any]) -> None:
    print(f"Updated: {participant['id']}")
    if participant['media'].get('camera', {}).get('state') == 'playable':
        print("Camera is now available")

Parameters

participant
Mapping[str, Any]
required
Dictionary containing updated participant information.

on_participant_left()

Called when a participant leaves the call.
def on_participant_left(self, participant: Mapping[str, Any], reason: str) -> None:
    print(f"Left: {participant['info']['userName']} - {reason}")

Parameters

participant
Mapping[str, Any]
required
Dictionary containing information about the participant who left.
reason
str
required
Reason for leaving (e.g., “leftCall”, “hidden”, “ejected”).

on_participant_counts_updated()

Called when participant counts change.
def on_participant_counts_updated(self, counts: Mapping[str, Any]) -> None:
    print(f"Present: {counts['present']}, Hidden: {counts['hidden']}")

Parameters

counts
Mapping[str, Any]
required
Dictionary containing participant counts:
  • present - Number of visible participants
  • hidden - Number of hidden participants

on_active_speaker_changed()

Called when the active speaker changes.
def on_active_speaker_changed(self, participant: Mapping[str, Any]) -> None:
    print(f"Active speaker: {participant.get('participantId')}")

Parameters

participant
Mapping[str, Any]
required
Dictionary containing active speaker information including participantId.

Media Events

on_inputs_updated()

Called when input settings (camera/microphone) are updated.
def on_inputs_updated(self, input_settings: Mapping[str, Any]) -> None:
    print(f"Camera enabled: {input_settings['camera']['isEnabled']}")
    print(f"Mic enabled: {input_settings['microphone']['isEnabled']}")

Parameters

input_settings
Mapping[str, Any]
required
Dictionary containing updated input settings for camera and microphone.

on_publishing_updated()

Called when publishing settings are updated.
def on_publishing_updated(self, publishing_settings: Mapping[str, Any]) -> None:
    print(f"Publishing camera: {publishing_settings['camera']['isPublishing']}")

Parameters

publishing_settings
Mapping[str, Any]
required
Dictionary containing updated publishing settings.

on_subscriptions_updated()

Called when subscription settings are updated.
def on_subscriptions_updated(self, subscriptions: Mapping[str, Any]) -> None:
    for participant_id, settings in subscriptions.items():
        print(f"{participant_id}: {settings}")

Parameters

subscriptions
Mapping[str, Any]
required
Dictionary mapping participant IDs to their subscription settings.

on_subscription_profiles_updated()

Called when subscription profiles are updated.
def on_subscription_profiles_updated(self, subscription_profiles: Mapping[str, Any]) -> None:
    print(f"Profiles: {subscription_profiles}")

Parameters

subscription_profiles
Mapping[str, Any]
required
Dictionary containing updated subscription profile configurations.

on_available_devices_updated()

Called when the list of available media devices changes.
def on_available_devices_updated(self, available_devices: Mapping[str, Any]) -> None:
    print(f"Cameras: {len(available_devices.get('camera', []))}")
    print(f"Microphones: {len(available_devices.get('microphone', []))}")

Parameters

available_devices
Mapping[str, Any]
required
Dictionary containing lists of available devices by type (camera, microphone, speaker).

Recording Events

on_recording_started()

Called when recording starts.
def on_recording_started(self, status: Mapping[str, Any]) -> None:
    print(f"Recording started: {status.get('streamId')}")

Parameters

status
Mapping[str, Any]
required
Dictionary containing recording status information including streamId.

on_recording_stopped()

Called when recording stops.
def on_recording_stopped(self, stream_id: str) -> None:
    print(f"Recording stopped: {stream_id}")

Parameters

stream_id
str
required
The ID of the recording stream that stopped.

on_recording_error()

Called when a recording error occurs.
def on_recording_error(self, stream_id: str, message: str) -> None:
    print(f"Recording error on {stream_id}: {message}")

Parameters

stream_id
str
required
The ID of the recording stream that encountered an error.
message
str
required
Error message describing what went wrong.

Live Streaming Events

on_live_stream_started()

Called when live streaming starts.
def on_live_stream_started(self, status: Mapping[str, Any]) -> None:
    print(f"Stream started: {status.get('streamId')}")

Parameters

status
Mapping[str, Any]
required
Dictionary containing live stream status information.

on_live_stream_updated()

Called when live stream settings are updated.
def on_live_stream_updated(self, state: str) -> None:
    print(f"Stream state: {state}")

Parameters

state
str
required
The updated stream state.

on_live_stream_stopped()

Called when live streaming stops.
def on_live_stream_stopped(self, stream_id: str) -> None:
    print(f"Stream stopped: {stream_id}")

Parameters

stream_id
str
required
The ID of the stream that stopped.

on_live_stream_error()

Called when a live streaming error occurs.
def on_live_stream_error(self, stream_id: str, message: str) -> None:
    print(f"Stream error on {stream_id}: {message}")

Parameters

stream_id
str
required
The ID of the stream that encountered an error.
message
str
required
Error message describing what went wrong.

on_live_stream_warning()

Called when a live streaming warning occurs.
def on_live_stream_warning(self, stream_id: str, message: str) -> None:
    print(f"Stream warning on {stream_id}: {message}")

Parameters

stream_id
str
required
The ID of the stream that generated the warning.
message
str
required
Warning message.

Transcription Events

on_transcription_started()

Called when transcription starts.
def on_transcription_started(self, status: Mapping[str, Any]) -> None:
    print(f"Transcription started: {status}")

Parameters

status
Mapping[str, Any]
required
Dictionary containing transcription status information.

on_transcription_message()

Called when a transcription message is received.
def on_transcription_message(self, message: Mapping[str, Any]) -> None:
    participant_id = message.get('participantId')
    text = message.get('text')
    is_final = message.get('isFinal', False)
    print(f"{participant_id}: {text} (final={is_final})")

Parameters

message
Mapping[str, Any]
required
Dictionary containing transcription message data:
  • participantId - ID of the speaking participant
  • text - Transcribed text
  • isFinal - Whether this is a final transcription
  • timestamp - Message timestamp

on_transcription_stopped()

Called when transcription stops.
def on_transcription_stopped(self, stopped_by: str, stopped_by_error: bool) -> None:
    print(f"Transcription stopped by {stopped_by}, error={stopped_by_error}")

Parameters

stopped_by
str
required
Identifier of who/what stopped the transcription.
stopped_by_error
bool
required
Whether transcription was stopped due to an error.

on_transcription_error()

Called when a transcription error occurs.
def on_transcription_error(self, message: str) -> None:
    print(f"Transcription error: {message}")

Parameters

message
str
required
Error message describing what went wrong.

SIP/Dialout Events

on_dialin_ready()

Called when dial-in is ready.
def on_dialin_ready(self, sip_endpoint: str) -> None:
    print(f"Dial in to: {sip_endpoint}")

Parameters

sip_endpoint
str
required
SIP endpoint URI for dialing in.

on_dialout_connected()

Called when a dialout connection is established.
def on_dialout_connected(self, data: Mapping[str, Any]) -> None:
    print(f"Dialout connected: {data}")

Parameters

data
Mapping[str, Any]
required
Dictionary containing dialout connection information.

on_dialout_answered()

Called when a dialout call is answered.
def on_dialout_answered(self, data: Mapping[str, Any]) -> None:
    print(f"Dialout answered: {data}")

Parameters

data
Mapping[str, Any]
required
Dictionary containing dialout answer information.

on_dialout_stopped()

Called when a dialout call stops.
def on_dialout_stopped(self, data: Mapping[str, Any]) -> None:
    print(f"Dialout stopped: {data}")

Parameters

data
Mapping[str, Any]
required
Dictionary containing dialout stop information.

on_dialout_error()

Called when a dialout error occurs.
def on_dialout_error(self, data: Mapping[str, Any]) -> None:
    print(f"Dialout error: {data}")

Parameters

data
Mapping[str, Any]
required
Dictionary containing dialout error information.

on_dialout_warning()

Called when a dialout warning occurs.
def on_dialout_warning(self, data: Mapping[str, Any]) -> None:
    print(f"Dialout warning: {data}")

Parameters

data
Mapping[str, Any]
required
Dictionary containing dialout warning information.

Messaging Events

on_app_message()

Called when an application message is received.
def on_app_message(self, message: Any, sender: str) -> None:
    print(f"Message from {sender}: {message}")

Parameters

message
Any
required
The message payload (can be any type sent via send_app_message()).
sender
str
required
Participant ID of the message sender.

Network Events

on_network_stats_updated()

Called when network statistics are updated.
def on_network_stats_updated(self, stats: Mapping[str, Any]) -> None:
    latest = stats.get('stats', {}).get('latest', {})
    print(f"Send bitrate: {latest.get('videoSendBitsPerSecond')}")
    print(f"Receive bitrate: {latest.get('videoRecvBitsPerSecond')}")

Parameters

stats
Mapping[str, Any]
required
Dictionary containing network statistics including bitrates, packet loss, and latency.

Complete Example

from daily import Daily, CallClient, EventHandler, LogLevel
from typing import Mapping, Any

class BotEventHandler(EventHandler):
    """Custom event handler for a Daily bot."""
    
    def __init__(self):
        super().__init__()
        self.participants = {}
    
    def on_call_state_updated(self, state: str) -> None:
        print(f"[Call] State changed to: {state}")
    
    def on_participant_joined(self, participant: Mapping[str, Any]) -> None:
        participant_id = participant['id']
        user_name = participant.get('info', {}).get('userName', 'Unknown')
        self.participants[participant_id] = participant
        print(f"[Participant] {user_name} joined (ID: {participant_id})")
    
    def on_participant_left(self, participant: Mapping[str, Any], reason: str) -> None:
        participant_id = participant['id']
        user_name = participant.get('info', {}).get('userName', 'Unknown')
        if participant_id in self.participants:
            del self.participants[participant_id]
        print(f"[Participant] {user_name} left - {reason}")
    
    def on_participant_updated(self, participant: Mapping[str, Any]) -> None:
        participant_id = participant['id']
        self.participants[participant_id] = participant
        print(f"[Participant] {participant_id} updated")
    
    def on_active_speaker_changed(self, participant: Mapping[str, Any]) -> None:
        speaker_id = participant.get('participantId')
        if speaker_id and speaker_id in self.participants:
            user_name = self.participants[speaker_id].get('info', {}).get('userName', 'Unknown')
            print(f"[Audio] Active speaker: {user_name}")
    
    def on_transcription_message(self, message: Mapping[str, Any]) -> None:
        participant_id = message.get('participantId')
        text = message.get('text', '')
        is_final = message.get('isFinal', False)
        
        user_name = 'Unknown'
        if participant_id and participant_id in self.participants:
            user_name = self.participants[participant_id].get('info', {}).get('userName', 'Unknown')
        
        status = "[FINAL]" if is_final else "[PARTIAL]"
        print(f"[Transcription] {status} {user_name}: {text}")
    
    def on_app_message(self, message: Any, sender: str) -> None:
        user_name = 'Unknown'
        if sender in self.participants:
            user_name = self.participants[sender].get('info', {}).get('userName', 'Unknown')
        print(f"[Message] {user_name}: {message}")
    
    def on_error(self, message: str) -> None:
        print(f"[Error] {message}")
    
    def on_recording_started(self, status: Mapping[str, Any]) -> None:
        stream_id = status.get('streamId', 'unknown')
        print(f"[Recording] Started - {stream_id}")
    
    def on_recording_stopped(self, stream_id: str) -> None:
        print(f"[Recording] Stopped - {stream_id}")
    
    def on_network_stats_updated(self, stats: Mapping[str, Any]) -> None:
        # Optionally log network stats (can be verbose)
        pass

# Usage
Daily.init(log_level=LogLevel.Info)

event_handler = BotEventHandler()
client = CallClient(event_handler=event_handler)

client.join(
    meeting_url="https://your-domain.daily.co/room",
    client_settings={"userName": "Bot"}
)

# ... run your bot logic ...

client.leave()
Daily.deinit()

Build docs developers (and LLMs) love