Human Handover Protocol
KAIU’s AI system automatically detects when a customer needs human assistance and seamlessly transfers the conversation to a live agent. This ensures complex issues, complaints, or explicit requests for human support are handled appropriately.
Handover Flow
Keyword Detection
Implementation (queue.js:86-116)
// --- HANDOVER CHECK ---
const HANDOVER_KEYWORDS = / \b ( humano | agente | asesor | persona | queja | reclamo | ayuda | contactar | hablar con alguien ) \b / i ;
if ( HANDOVER_KEYWORDS . test ( text )) {
console . log ( `🚨 Handover triggered for ${ from } by text: " ${ text } "` );
// 1. Disable Bot
await prisma . whatsAppSession . update ({
where: { id: session . id },
data: {
isBotActive: false ,
handoverTrigger: "KEYWORD_DETECTED" ,
sessionContext: { ... session . sessionContext , history } // Save history including trigger msg
}
});
// Emit Status Update
if ( io ) io . emit ( 'session_update' , { id: session . id , status: 'handover' });
// 2. Send "Connect to Agent" Message
await axios . post (
`https://graph.facebook.com/v21.0/ ${ process . env . WHATSAPP_PHONE_ID } /messages` ,
{
messaging_product: "whatsapp" ,
to: from ,
text: { body: "Te estoy transfiriendo con un asesor humano. Un momento por favor." }
},
{ headers: {
'Authorization' : `Bearer ${ process . env . WHATSAPP_ACCESS_TOKEN } ` ,
'Content-Type' : 'application/json'
}}
);
console . log ( `✅ Handover executed for ${ from } ` );
return ; // STOP AI PROCESSING
}
Trigger Keywords
The system detects these Spanish keywords (case-insensitive):
Category Keywords Human Request humano, agente, asesor, personaComplaints queja, reclamoHelp Requests ayuda, contactarPhrases hablar con alguien
The regex uses word boundaries (\b) to prevent false positives from partial matches like “humanoid” or “agencia”.
Example Conversations
Explicit Request
Complaint
Help Request
No Match
User: "Quiero hablar con un agente humano"
→ HANDOVER TRIGGERED
→ Bot disabled
→ Response: "Te estoy transfiriendo con un asesor humano. Un momento por favor."
Database State
WhatsAppSession Model
model WhatsAppSession {
id String @id @default ( uuid ())
phoneNumber String @unique
isBotActive Boolean @default ( true ) // Set to false on handover
sessionContext Json ? // Preserves conversation history
handoverTrigger String ? // "KEYWORD_DETECTED" | "MANUAL" | "TIMEOUT"
expiresAt DateTime
userId String ? @unique
user User ? @relation ( fields : [ userId ], references : [ id ] )
createdAt DateTime @default ( now ())
updatedAt DateTime @updatedAt
}
State Transitions
Field Before Handover After Handover isBotActivetruefalsehandoverTriggernull"KEYWORD_DETECTED"sessionContext.historyArray of messages Preserved (includes trigger)
Real-time Dashboard Updates
Socket.IO Events (queue.js:101)
if ( io ) {
io . emit ( 'session_update' , {
id: session . id ,
status: 'handover'
});
}
The admin dashboard receives this event and:
Highlights the session in the sidebar
Shows a red “Handover” badge
Plays an optional alert sound
Opens the conversation for agent response
Bot Deactivation Behavior
Incoming Messages After Handover (queue.js:56-59)
if ( ! session . isBotActive ) {
console . log ( `⏸️ Bot inactive for ${ from } . Skipping.` );
return ; // Job completes without AI processing
}
Once isBotActive is set to false:
Messages are still received and stored
No AI processing occurs
Agent dashboard shows all messages
Agent can respond manually via dashboard
The bot will NOT automatically re-activate. An admin must manually re-enable it via the dashboard or API.
Manual Re-activation
To re-enable the bot after resolution:
// Admin API endpoint
await prisma . whatsAppSession . update ({
where: { id: sessionId },
data: {
isBotActive: true ,
handoverTrigger: null
}
});
Handover Message Customization
Edit the transfer message in queue.js:109:
await axios . post (
`https://graph.facebook.com/v21.0/ ${ process . env . WHATSAPP_PHONE_ID } /messages` ,
{
messaging_product: "whatsapp" ,
to: from ,
text: {
body: "Te estoy transfiriendo con un asesor humano. Un momento por favor."
}
},
{ headers: {
'Authorization' : `Bearer ${ process . env . WHATSAPP_ACCESS_TOKEN } ` ,
'Content-Type' : 'application/json'
}}
);
Adding Custom Keywords
English Support
const HANDOVER_KEYWORDS = / \b ( humano | agente | asesor | persona | queja | reclamo | ayuda | contactar | hablar con alguien | human | agent | help | support | speak to someone ) \b / i ;
Category-Specific Triggers
const URGENT_KEYWORDS = / \b ( urgente | emergencia | critico ) \b / i ;
const COMPLAINT_KEYWORDS = / \b ( queja | reclamo | insatisfecho | devolucion ) \b / i ;
if ( URGENT_KEYWORDS . test ( text )) {
handoverTrigger = "URGENT_DETECTED" ;
} else if ( COMPLAINT_KEYWORDS . test ( text )) {
handoverTrigger = "COMPLAINT_DETECTED" ;
} else if ( HANDOVER_KEYWORDS . test ( text )) {
handoverTrigger = "KEYWORD_DETECTED" ;
}
Advanced: Sentiment-Based Handover
For production, consider adding sentiment analysis:
import Sentiment from 'sentiment' ;
const sentiment = new Sentiment ();
const result = sentiment . analyze ( text );
if ( result . score < - 3 ) {
// Very negative sentiment
console . log ( `🚨 Negative sentiment detected: ${ result . score } ` );
handoverTrigger = "NEGATIVE_SENTIMENT" ;
// Trigger handover
}
Conversation History Preservation
The full conversation history is preserved on handover:
await prisma . whatsAppSession . update ({
where: { id: session . id },
data: {
isBotActive: false ,
handoverTrigger: "KEYWORD_DETECTED" ,
sessionContext: {
... session . sessionContext ,
history // Includes the trigger message
}
}
});
This allows the human agent to:
See the full context
Understand why handover was triggered
Continue the conversation seamlessly
Handover Analytics
Track handover metrics:
-- Count handovers by trigger
SELECT
"handoverTrigger" ,
COUNT ( * ) as count,
AVG (EXTRACT(EPOCH FROM ( "updatedAt" - "createdAt" ))) as avg_duration_seconds
FROM whatsapp_sessions
WHERE "handoverTrigger" IS NOT NULL
GROUP BY "handoverTrigger"
ORDER BY count DESC ;
Best Practices
Preserve Context Always save conversation history on handover so agents have full context
Immediate Response Send transfer message instantly to acknowledge the handover request
Clear Keywords Use unambiguous keywords to prevent false positives
Alert Agents Use real-time notifications to ensure agents see handover requests
Testing Handovers
// Test messages
const testCases = [
"Quiero hablar con un agente" , // Should trigger
"Tengo una queja" , // Should trigger
"Necesito ayuda con mi pedido" , // Should trigger
"¿Tienen lavanda?" , // Should NOT trigger
"Esto es una humanidad" // Should NOT trigger (partial match)
];
for ( const text of testCases ) {
const matches = HANDOVER_KEYWORDS . test ( text );
console . log ( `" ${ text } " → ${ matches ? 'HANDOVER' : 'NORMAL' } ` );
}
Next Steps
Dashboard Integration Learn how agents receive handover notifications
PII Privacy Understand how sensitive data is protected during handovers