Overview
Explicitly ends a chat session and triggers all cleanup operations. Always call this endpoint when the user closes the chat or navigates away.
Authentication: Authorization: Bearer {session_token}
Important: Sessions auto-expire after 30 minutes of inactivity, but explicit ending ensures proper cleanup.
Request
Path Parameters:
Parameter Type Required Description conversation_idstring ✅ Session ID from create session response
Headers:
Authorization: Bearer {session_token}
Body: None
Response
Status: 200 OK
{
"success" : true ,
"message" : "Session ended successfully"
}
What Happens on End
When you call /end, the following cleanup operations run:
1. Billing Finalization
# End billing session
await end_billing_session(
conversation_id,
user_message_count = session.user_message_count,
total_message_count = session.total_message_count,
)
# Calculate session cost
session_cost = await get_session_cost(conversation_id)
# Consume credits from organization balance
await consume_credits(
organization_id = organization_id,
session_cost = session_cost,
session_id = billing_session_id
)
Result: Credits consumed based on actual usage
2. Memory Ingestion (Graphiti)
# Get all messages
messages = await get_messages(conversation_id, limit = 1000 )
# Format for Graphiti
graphiti_messages = [
{ "role" : msg[ "role" ], "content" : msg[ "content" ]}
for msg in messages
]
# Ingest into knowledge graph
result = await ingest_conversation_async(
session_id = conversation_id,
messages = graphiti_messages,
agent_id = agent_id,
org_id = organization_id,
user_id = memory_id # From user_identifier
)
Result: Conversation memory available for future sessions
Timeout: 25 seconds (fire-and-forget)
3. Post-Call Analysis
# Trigger analysis (fire-and-forget)
asyncio.create_task(
trigger_post_call_analysis(conversation_id, agent_id, user_id)
)
Result: QA analysis if configured on agent
4. Webhook Notification
# Call agent webhook if configured
if agent_config.webhook_url:
await call_agent_webhook(
conversation_id,
agent_config,
user_id,
event_type = "chat"
)
Webhook Payload:
{
"event" : "chat.ended" ,
"timestamp" : "2024-01-15T10:35:00Z" ,
"data" : {
"conversation_id" : "chat_abc123" ,
"agent_id" : "agent_xyz" ,
"agent_name" : "Support Bot" ,
"message_count" : 20 ,
"input_tokens" : 150 ,
"output_tokens" : 300 ,
"total_cost" : 0.0045
}
}
Timeout: 7 seconds
5. Session Removal
# Remove from in-memory store
removed = await session_store.end_session(conversation_id)
Result: Session removed from memory, conversation input cleared
Error Responses
401 Unauthorized
{
"detail" : "Invalid token"
}
403 Forbidden
{
"detail" : "Conversation ID does not match session token"
}
500 Server Error
{
"detail" : "Failed to end session"
}
Examples
cURL
curl -X POST "https://api.talkifai.dev/v1/chat/sessions/chat_abc123/end" \
-H "Authorization: Bearer eyJhbGc..."
JavaScript (Fetch)
async function endSession ( conversationId , sessionToken ) {
try {
const response = await fetch (
`https://api.talkifai.dev/v1/chat/sessions/ ${ conversationId } /end` ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ sessionToken } `
}
}
);
if ( ! response . ok ) {
throw new Error ( 'Failed to end session' );
}
console . log ( 'Session ended successfully' );
} catch ( error ) {
console . error ( 'Error ending session:' , error );
}
}
React (Cleanup on Unmount)
useEffect (() => {
return () => {
// Cleanup when component unmounts
if ( sessionTokenRef . current && conversationIdRef . current ) {
fetch (
`/v1/chat/sessions/ ${ conversationIdRef . current } /end` ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ sessionTokenRef . current } `
}
}
). catch ( err => console . error ( 'Cleanup error:' , err ));
}
};
}, []);
React (With keepalive for Tab Close)
async function endSession ( conversationId , token ) {
await fetch ( `/v1/chat/sessions/ ${ conversationId } /end` , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } `
},
keepalive: true // Critical: survives tab close
});
}
beforeunload Handler
window . addEventListener ( 'beforeunload' , () => {
if ( sessionToken && conversationId ) {
fetch ( `/v1/chat/sessions/ ${ conversationId } /end` , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ sessionToken } `
},
keepalive: true // Ensures request completes
});
}
});
Next.js (Route Handler)
// app/api/chat/end/route.ts
export async function POST ( request : Request ) {
const { conversationId , sessionToken } = await request . json ();
try {
const response = await fetch (
` ${ process . env . TALKIFAI_API_URL } /v1/chat/sessions/ ${ conversationId } /end` ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ sessionToken } `
}
}
);
const data = await response . json ();
if ( ! response . ok ) {
return Response . json ( data , { status: response . status });
}
return Response . json ( data );
} catch ( error ) {
return Response . json (
{ error: 'Failed to end session' },
{ status: 500 }
);
}
}
When to Call /end
✅ Always Call When:
User Closes Chat Widget
onCloseChatWidget = () => {
endSession ( conversationId , token );
hideWidget ();
}
User Navigates Away
window . addEventListener ( 'beforeunload' , handleCleanup );
Agent Calls end_chat Tool
if ( eventType === 'stream_end' && data . end_session ) {
await endSession ( conversationId , token );
}
Session Timeout (Idle)
Handled automatically by server
But explicit is better than implicit
❌ Don’t Call When:
Session Already Expired
Token invalid, will return 401
No cleanup needed
Mid-Conversation
User is still chatting
Wait for natural end
Auto-Expiry vs Explicit End
Aspect Auto-Expiry (30 min) Explicit /end Trigger Inactivity timeout User action Billing Finalized Finalized Memory Ingested Ingested Analysis Triggered Triggered Webhook Fired Fired Reliability Depends on cleanup loop Immediate
Always call /end explicitly. Auto-expiry relies on background cleanup loops that may have delays. Explicit ending ensures immediate cleanup.
Best Practices
1. Use keepalive
// ✅ Good
fetch ( '/end' , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ token } ` },
keepalive: true // Survives tab close
});
// ❌ Bad (may be cancelled by browser)
fetch ( '/end' , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ token } ` }
});
2. Handle Errors Gracefully
try {
await endSession ( conversationId , token );
} catch ( error ) {
// Log but don't show to user
console . error ( 'Cleanup error (non-fatal):' , error );
// Session will auto-expire anyway
}
3. Clear Local State
async function endSession ( conversationId , token ) {
await fetch ( `/v1/chat/sessions/ ${ conversationId } /end` , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ token } ` },
keepalive: true
});
// Clear local state
sessionStorage . removeItem ( 'conversation_id' );
sessionStorage . removeItem ( 'session_token' );
setMessages ([]);
}
4. Debounce Rapid Calls
const endSessionRef = useRef ( null );
const endSessionWithDebounce = useCallback (( conversationId , token ) => {
if ( endSessionRef . current ) return ; // Already ending
endSessionRef . current = setTimeout ( async () => {
await endSession ( conversationId , token );
endSessionRef . current = null ;
}, 1000 );
}, []);
Next Steps
Create New Session Start a fresh conversation with a new session.
View Analytics Check session costs and usage in dashboard.