Skip to main content

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

  1. Open your Twilio Studio Flow
  2. Add Run Function widget after call start
  3. Configure:
    • Function: get-caller-context
    • Parameters:
      • From: {{trigger.call.From}}
      • CallSid: {{trigger.call.CallSid}}

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

  1. Async Function Calls - Don't block call flow
  2. Timeout Handling - 5 second timeout recommended
  3. Error Handling - Gracefully degrade if API fails
  4. Cache Context - Store in Task attributes for agent access
  5. Visual Priority - Highlight high-confidence matches

Testing

  1. Make test call to Flex number
  2. Verify function executes
  3. Check TaskRouter attributes set
  4. Confirm screen pop displays
  5. Complete call and verify context saved

Ready to add caller context to Twilio Flex? Sign up →