Voice Call Events Voice call events fire during and after voice calls in your Mirra account. These events provide call metadata, real-time transcriptions, and AI-generated summaries, with support for ticker detection and sentiment analysis in transcripts.
The call.started event fires when a voice call begins. This event captures call metadata and participant information.
Event Type:
type : 'call.started'
source : 'call'
Fields:
content.text (string)Empty string for call start events.
content.contentType (ContentType)Always 'call' for voice call events.
call.agoraCallId (string)Agora RTC call identifier.
call.mirraCallId (string)Mirra internal call identifier.
call.chatInstanceId (string)Associated chat instance ID.
call.participants (CallParticipant[])Array of participants in the call.
Field Type Description userIdstring User's Mirra ID usernamestring User's display name joinedAtDate When participant joined leftAtDate | null When participant left (null if still in call) agoraUidnumber | null Agora user ID
call.isRecording (boolean)True if the call is being recorded.
Example - Call Logger:
export async function handler ( event , context ) {
// Log call start
await mirra.memory. create ({
type: 'call_log' ,
content: `Call started with ${ event . call . participants . length } participants` ,
metadata: {
callId: event.call.agoraCallId,
participants: event.call.participants. map ( p => p.username),
startTime: event.timestamp,
isRecording: event.call.isRecording
}
});
return { success: true };
}
Example - Call Notification:
export async function handler ( event , context ) {
// Notify about call start
const participantNames = event.call.participants
. map ( p => p.username)
. join ( ', ' );
await mirra.telegram. sendMessage ({
chatId: 'your-chat-id' ,
text: `📞 Call started \n\n Participants: ${ participantNames } \n ${ event . call . isRecording ? '🔴 Recording' : ''}`
});
return { success: true };
}
The call.ended event fires when a voice call ends. This event includes complete call metadata and duration.
Event Type:
type : 'call.ended'
source : 'call'
Fields:
call.agoraCallId (string)Agora RTC call identifier.
call.mirraCallId (string)Mirra internal call identifier.
call.chatInstanceId (string)Associated chat instance ID.
call.participants (CallParticipant[])Array of all participants who were in the call.
call.durationSeconds (number)Total call duration in seconds.
call.startedAt (Date)When the call started.
call.endedAt (Date)When the call ended.
call.wasRecorded (boolean)True if the call was recorded.
call.recordingUrl (string | null)URL to the call recording if available, null otherwise.
Example - Call Analytics:
export async function handler ( event , context ) {
// Calculate call metrics
const durationMinutes = Math. round (event.call.durationSeconds / 60 );
// Log to spreadsheet
await mirra.google.sheets. appendRow ({
spreadsheetId: 'your-sheet-id' ,
sheetName: 'Call Logs' ,
values: [
event.call.startedAt. toISOString (),
event.call.participants. map ( p => p.username). join ( ', ' ),
event.call.participants. length ,
durationMinutes,
event.call.wasRecorded ? 'Yes' : 'No' ,
event.call.recordingUrl || 'N/A'
]
});
return { success: true };
}
Example - Call Summary:
export async function handler ( event , context ) {
// Create summary memory
const durationMinutes = Math. round (event.call.durationSeconds / 60 );
await mirra.memory. create ({
type: 'call_summary' ,
content: `Call with ${ event . call . participants . length } participants lasted ${ durationMinutes } minutes` ,
metadata: {
callId: event.call.mirraCallId,
participants: event.call.participants. map ( p => ({
username: p.username,
joinedAt: p.joinedAt,
leftAt: p.leftAt
})),
duration: event.call.durationSeconds,
recordingUrl: event.call.recordingUrl
}
});
return { success: true };
}
The transcript.chunk event fires when a voice transcript chunk is processed in real-time. This event supports automatic enrichment including ticker detection and sentiment analysis.
Event Type:
type : 'transcript.chunk'
source : 'call'
Fields:
content.text (string)Transcribed text for this chunk.
content.contentType (ContentType)Always 'transcript' for transcript events.
call.agoraCallId (string)Call identifier.
call.mirraCallId (string)Mirra internal call identifier.
transcript.speakerUserId (string)User ID of the speaker.
transcript.speakerName (string)Display name of the speaker.
transcript.text (string)Transcribed text (same as content.text).
transcript.startTimeMs (number)Start time of this chunk in milliseconds from call start.
transcript.endTimeMs (number)End time of this chunk in milliseconds from call start.
transcript.confidence (number)Transcription confidence score from 0 to 1.
enrichment (EventEnrichment | null)Enrichment data including detected tickers and sentiment. Null until processing completes.
Example - Ticker Tracker:
export async function handler ( event , context ) {
// Wait for enrichment
if ( ! event.enrichment?.tickers) {
return { success: true , message: 'No tickers detected' };
}
// Track ticker mentions in calls
for ( const ticker of event.enrichment.tickers) {
await mirra.memory. create ({
type: 'call_ticker_mention' ,
content: `${ ticker . symbol } mentioned by ${ event . transcript . speakerName }: ${ event . content . text }` ,
metadata: {
ticker: ticker.symbol,
confidence: ticker.confidence,
speaker: event.transcript.speakerName,
callId: event.call.mirraCallId,
timestamp: event.transcript.startTimeMs
}
});
}
return {
success: true ,
tickersProcessed: event.enrichment.tickers. length
};
}
Example - Sentiment Monitoring:
export async function handler ( event , context ) {
if ( ! event.enrichment?.sentiment) {
return { success: true };
}
const sentiment = event.enrichment.sentiment;
// Alert on negative sentiment
if (sentiment.label === 'negative' && sentiment.score < - 0.5 ) {
await mirra.telegram. sendMessage ({
chatId: 'your-alerts-channel' ,
text: `⚠️ Negative sentiment in call \n\n Speaker: ${ event . transcript . speakerName } \n Score: ${ sentiment . score } \n\n "${ event . content . text }"`
});
}
return { success: true };
}
Subscription Examples:
// All transcript chunks
{
"eventType" : "transcript.chunk"
}
// Only chunks with tickers
{
"eventType" : "transcript.chunk" ,
"conditions" : [
{
"field" : "enrichment.tickers" ,
"operator" : "exists"
}
]
}
// Specific speaker
{
"eventType" : "transcript.chunk" ,
"conditions" : [
{
"field" : "transcript.speakerName" ,
"operator" : "equals" ,
"value" : "John Doe"
}
]
}
The call.summary_available event fires when an AI-generated call summary is ready.
Event Type:
type : 'call.summary_available'
source : 'call'
Fields:
content.text (string)AI-generated summary text.
call.agoraCallId (string)Call identifier.
call.mirraCallId (string)Mirra internal call identifier.
call.summary (string)AI-generated call summary (same as content.text).
Example - Summary Distribution:
export async function handler ( event , context ) {
// Send summary to Telegram
await mirra.telegram. sendMessage ({
chatId: 'your-team-channel' ,
text: `📞 Call Summary \n\n ${ event . call . summary }`
});
// Save to memory
await mirra.memory. create ({
type: 'call_summary' ,
content: event.call.summary,
metadata: {
callId: event.call.mirraCallId,
generatedAt: event.timestamp
}
});
return { success: true };
}
export async function handler ( event , context ) {
// Only track ended calls
if (event.type !== 'call.ended' ) {
return { success: true , skipped: true };
}
// Calculate billable time
const durationHours = event.call.durationSeconds / 3600 ;
const roundedHours = Math. ceil (durationHours * 4 ) / 4 ; // Round to nearest 15 min
// Log to time tracking
await mirra.google.sheets. appendRow ({
spreadsheetId: 'time-tracking-sheet' ,
sheetName: 'Calls' ,
values: [
event.call.startedAt. toISOString (). split ( 'T' )[ 0 ],
'Call' ,
event.call.participants. filter ( p => p.userId !== event.userId). map ( p => p.username). join ( ', ' ),
roundedHours,
'Billable'
]
});
return { success: true };
}
export async function handler ( event , context ) {
// Wait for summary
if (event.type !== 'call.summary_available' ) {
return { success: true , skipped: true };
}
// Create Google Doc with meeting minutes
const doc = await mirra.google.docs. create ({
title: `Meeting Minutes - ${ new Date (). toLocaleDateString () }` ,
content: `# Meeting Minutes \n\n Date: ${ new Date (). toLocaleDateString () } \n Duration: ${ Math . round ( event . call . durationSeconds / 60 ) } minutes \n\n ## Summary \n\n ${ event . call . summary } \n\n ## Participants \n\n ${ event . call . participants . map ( p => `- ${ p . username }` ). join ( ' \n ' ) }`
});
// Share link via Telegram
await mirra.telegram. sendMessage ({
chatId: 'your-team-channel' ,
text: `📝 Meeting minutes available: ${ doc . url }`
});
return { success: true };
}
export async function handler ( event , context ) {
// Check transcript for action items
const text = event.content.text. toLowerCase ();
// Look for action-oriented phrases
const hasActionWords = [ 'todo' , 'action item' , 'follow up' , 'will do' , 'need to' ]. some (
phrase => text. includes (phrase)
);
if ( ! hasActionWords) {
return { success: true , skipped: true };
}
// Create assignment
await mirra.assignments. create ({
title: `Action from call: ${ event . content . text . substring ( 0 , 50 ) }...` ,
description: `From ${ event . transcript . speakerName } in call: \n\n "${ event . content . text }"` ,
metadata: {
callId: event.call.mirraCallId,
speaker: event.transcript.speakerName,
timestamp: event.transcript.startTimeMs
}
});
return { success: true };
}