Twilio Flex Integration
Integrate Sticky Calls API with Twilio Flex for agent screen pops with caller context.
Architecture
Incoming Call → Twilio Function → Sticky Calls API →
TaskRouter Attributes → Flex UI → Agent Screen Pop
Step 1: Create Twilio Function
Function: Get Caller Context
exports.handler = async function(context, event, callback) {
const axios = require('axios');
const callerPhone = event.From;
const callSid = event.CallSid;
try {
// Call Sticky Calls API
const response = await axios.post(
'https://api.stickycalls.com/v1/calls/start',
{
call_id: callSid,
identity_hints: {
ani: callerPhone,
external_ids: {
twilio_call_sid: callSid
}
}
},
{
headers: {
'Authorization': `Bearer ${context.STICKY_CALLS_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
const data = response.data;
const identity = data.identity || {};
// Return attributes for TaskRouter
callback(null, {
customer_ref: data.customer_ref || '',
identity_confidence: identity.confidence || 0,
identity_level: identity.level || 'none',
variables: JSON.stringify(data.variables || {}),
open_intents: JSON.stringify(data.open_intents || []),
call_start: data.call_start || ''
});
} catch (error) {
console.error('Sticky Calls API error:', error);
callback(null, {
customer_ref: '',
identity_confidence: 0,
identity_level: 'none',
variables: '{}'
});
}
};
Step 2: Update Studio Flow
Add Function Widget
- Open your Twilio Studio Flow
- Add Run Function widget after call start
- Configure:
- Function:
get-caller-context - Parameters:
From:{{trigger.call.From}}CallSid:{{trigger.call.CallSid}}
- Function:
Set Task Attributes
Add "Set Variables" widget:
{
"customer_ref": "{{widgets.get_context.parsed.customer_ref}}",
"identity_confidence": "{{widgets.get_context.parsed.identity_confidence}}",
"identity_level": "{{widgets.get_context.parsed.identity_level}}",
"variables": "{{widgets.get_context.parsed.variables}}",
"open_intents": "{{widgets.get_context.parsed.open_intents}}"
}
Step 3: Customize Flex UI
Plugin: Display Caller Context
import * as FlexPlugin from '@twilio/flex-plugin';
export default class CallerContextPlugin extends FlexPlugin {
constructor() {
super('CallerContextPlugin');
}
async init(flex, manager) {
// Add caller context panel
flex.TaskCanvas.Content.add(
<CallerContextPanel key="caller-context" />,
{
sortOrder: 1
}
);
}
}
// CallerContextPanel component
const CallerContextPanel = ({ task }) => {
const attributes = task.attributes;
const confidence = parseFloat(attributes.identity_confidence || 0);
const identityLevel = attributes.identity_level;
const variables = JSON.parse(attributes.variables || '{}');
const openIntents = JSON.parse(attributes.open_intents || '[]');
if (confidence < 0.5) {
return <div>New caller - no previous context</div>;
}
return (
<div className="caller-context-panel">
<h3>Caller History</h3>
<div className="confidence-badge">
Confidence: {(confidence * 100).toFixed(0)}% ({identityLevel})
</div>
<div className="context-section">
<strong>Previous Variables:</strong>
<pre>{JSON.stringify(variables, null, 2)}</pre>
</div>
{openIntents.length > 0 && (
<div className="open-issues">
<strong>Open Issues:</strong>
<ul>
{openIntents.map((intent, i) => (
<li key={i}>{intent.intent} (attempt {intent.attempt_count})</li>
))}
</ul>
</div>
)}
</div>
);
};
Step 4: Save Context After Call
Twilio Function: Save Context
exports.handler = async function(context, event, callback) {
const axios = require('axios');
try {
await axios.post(
'https://api.stickycalls.com/v1/calls/end',
{
call_id: event.CallSid,
customer_ref: event.CustomerRef,
intent: event.Intent || 'general_inquiry',
intent_status: event.IntentStatus || 'closed',
variables: {
summary: event.AgentNotes || '',
disposition: event.Disposition || ''
}
},
{
headers: {
'Authorization': `Bearer ${context.STICKY_CALLS_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
callback(null, { success: true });
} catch (error) {
callback(error);
}
};
Trigger on Wrap-Up
After-Call Work (ACW) Plugin:
flex.Actions.addListener('afterCompleteTask', async (payload) => {
const task = payload.task;
const attributes = task.attributes;
// Save context to Sticky Calls
await fetch('https://your-domain.twilio.com/save-context', {
method: 'POST',
body: JSON.stringify({
CallSid: attributes.call_sid,
CustomerRef: attributes.customer_ref,
Intent: task.taskChannelUniqueName,
IntentStatus: task.openIssues?.length > 0 ? 'open' : 'closed',
AgentNotes: task.workspaceNote,
Disposition: task.disposition
})
});
});
Example Screen Pop
┌─ Caller Context (Confidence: 95% - very_high) ─┐
│ │
│ 📞 +1 (415) 555-1234 │
│ 🔗 Customer: cust_abc123 │
│ │
│ 📅 Last Contact: Jan 28, 2025 2:30 PM │
│ │
│ 📝 Previous Variables: │
│ • summary: "Billing issue - duplicate charge" │
│ • amount: "$50" │
│ • status: "pending_approval" │
│ │
│ ⚠️ Open Issues: │
│ • billing_refund (attempt 1) │
│ │
│ 💡 Suggested Action: │
│ Check refund status and inform customer │
└─────────────────────────────────────────────────┘
Best Practices
- Async Function Calls - Don't block call flow
- Timeout Handling - 5 second timeout recommended
- Error Handling - Gracefully degrade if API fails
- Cache Context - Store in Task attributes for agent access
- Visual Priority - Highlight high-confidence matches
Testing
- Make test call to Flex number
- Verify function executes
- Check TaskRouter attributes set
- Confirm screen pop displays
- Complete call and verify context saved
Ready to add caller context to Twilio Flex? Sign up →