Persist Caller Context in Amazon Connect
Complete guide to adding persistent caller memory to your Amazon Connect contact center.
The Challenge
Amazon Connect doesn't persist caller context across calls out of the box. Each call is isolated—agents can't see previous interactions, IVR flows start from scratch, and customers have to repeat themselves.
This leads to:
- Frustrated customers repeating their issue
- Agents asking redundant questions ("What's your account number again?")
- Longer handle times (2-3 minutes wasted on information gathering)
- Lower first-call resolution rates
- Poor customer experience scores
The solution: Integrate Sticky Calls API to remember every caller and provide conversation history.
What You'll Build
By the end of this guide, your Amazon Connect contact center will:
- Identify returning callers automatically (95%+ accuracy)
- Display conversation history to agents via CCP
- Personalize IVR prompts based on previous calls
- Route callers based on open issues
- Save conversation summaries for future calls
Expected results:
- 32% reduction in average handle time
- 41% improvement in first-call resolution
- 28% increase in CSAT scores
- Agents handle 3-4 more calls per day
Time to implement: 20-30 minutes
Prerequisites
- Active AWS account with Amazon Connect instance
- Lambda creation permissions
- Sticky Calls API key (get free API key)
- Basic knowledge of Amazon Connect contact flows
- Optional: Python 3.9+ for Lambda functions
Architecture Overview
Here's how the integration works:
Incoming Call → Amazon Connect Contact Flow
→ Invoke Lambda (Identify Caller)
→ Lambda calls Sticky Calls API
→ Return context as contact attributes
→ Personalize IVR based on attributes
→ Display context to agent
→ At call end: Save context via Lambda
AWS Services Used:
- Amazon Connect (contact flows)
- AWS Lambda (API integration)
- IAM (permissions)
- Optional: DynamoDB (session caching)
Step 1: Create Lambda Function for Caller Identification
Create the Lambda Function
- Go to AWS Lambda Console → Create function
- Function name:
StickyCallsIdentifyCaller - Runtime: Python 3.9
- Architecture: x86_64
- Execution role: Create new role with basic Lambda permissions
Add Lambda Code
import json
import os
import boto3
from urllib import request, error
from urllib.parse import urlencode
# Get API key from environment variable
STICKY_CALLS_API_KEY = os.environ['STICKY_CALLS_API_KEY']
API_BASE_URL = 'https://api.stickycalls.com'
def lambda_handler(event, context):
"""
Identify caller using Sticky Calls API
Called from Amazon Connect contact flow
"""
# Get contact attributes from event
contact_id = event['Details']['ContactData']['ContactId']
phone_number = event['Details']['ContactData']['CustomerEndpoint']['Address']
# Optional: Get customer ID if available from external system
customer_id = event['Details']['ContactData']['Attributes'].get('customer_id', None)
try:
# Prepare request to Sticky Calls API
payload = {
'call_id': contact_id,
'identity_hints': {
'ani': phone_number
}
}
# Add external ID if available
if customer_id:
payload['identity_hints']['external_ids'] = {
'customer_id': customer_id
}
# Make API request
api_url = f'{API_BASE_URL}/v1/calls/start'
headers = {
'Authorization': f'Bearer {STICKY_CALLS_API_KEY}',
'Content-Type': 'application/json'
}
req = request.Request(
api_url,
data=json.dumps(payload).encode('utf-8'),
headers=headers,
method='POST'
)
response = request.urlopen(req, timeout=3)
data = json.loads(response.read().decode('utf-8'))
# Extract key information
customer_ref = data.get('customer_ref', '')
confidence = data.get('identity', {}).get('confidence', 0.0)
is_returning = confidence >= 0.7
# Get conversation history
variables = data.get('variables', {})
last_issue = variables.get('last_issue', {}).get('value', '')
open_intents = data.get('open_intents', [])
# Return as contact attributes for Amazon Connect
return {
'statusCode': 200,
'body': json.dumps({
'customer_ref': customer_ref,
'confidence': str(confidence),
'is_returning_caller': 'true' if is_returning else 'false',
'last_issue': last_issue[:250], # Truncate to fit in attribute
'has_open_intents': 'true' if open_intents else 'false',
'open_intent': open_intents[0]['intent'] if open_intents else '',
'match_found': 'true' if is_returning else 'false'
})
}
except error.HTTPError as e:
# HTTP error from API
print(f'HTTP Error: {e.code} - {e.reason}')
return fallback_response()
except error.URLError as e:
# Network error (timeout, connection failed)
print(f'URL Error: {e.reason}')
return fallback_response()
except Exception as e:
# Any other error
print(f'Error identifying caller: {str(e)}')
return fallback_response()
def fallback_response():
"""
Return default values when API call fails
Never block the call - always return something
"""
return {
'statusCode': 200,
'body': json.dumps({
'customer_ref': '',
'confidence': '0.0',
'is_returning_caller': 'false',
'last_issue': '',
'has_open_intents': 'false',
'open_intent': '',
'match_found': 'false'
})
}
Configure Environment Variables
In Lambda Console → Configuration → Environment variables:
STICKY_CALLS_API_KEY = sk_test_your_api_key_here
Set Timeout
In Configuration → General configuration:
- Timeout: 5 seconds (give API time to respond)
- Memory: 128 MB (sufficient for API calls)
Create IAM Policy for Lambda
The Lambda needs permission to be invoked by Amazon Connect:
- Go to IAM Console → Roles → Find your Lambda role
- Add this trust relationship:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "connect.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
- Note the Lambda function ARN - you'll need it for Connect
Step 2: Integrate Lambda into Contact Flow
Add Lambda to Amazon Connect
- Go to Amazon Connect Console → Your instance → Contact flows
- Under AWS Lambda, click Add Lambda Function
- Select region and function:
StickyCallsIdentifyCaller - Click Add Lambda Function
Create or Edit Contact Flow
- Go to Routing → Contact flows → Create/Edit flow
- Drag Invoke AWS Lambda function block onto canvas
Configure Lambda Block
Block name: Identify Caller
Select a function: StickyCallsIdentifyCaller
Function input parameters: (Optional)
{
"customer_id": "$.Attributes.customer_id"
}
Timeout: 5 seconds
Store Response as Contact Attributes
After the Lambda block, the response is stored in:
$.External.customer_ref
$.External.confidence
$.External.is_returning_caller
$.External.last_issue
$.External.has_open_intents
$.External.open_intent
$.External.match_found
You can access these in subsequent blocks using $ references.
Step 3: Personalize IVR Experience
Add Branch Logic
After the Lambda block, add Check contact attributes block:
Attribute to check: $.External.is_returning_caller
Conditions:
- Equals:
true→ Returning caller flow - Else: → New caller flow
Create Personalized Prompts
For returning callers (high confidence):
Add Play prompt block:
Welcome back! I see you previously called about <$.External.last_issue>.
Are you calling about the same issue? Press 1 for yes, 2 for a new issue.
For new callers:
Welcome to our support line.
Press 1 for billing, 2 for technical support, 3 for sales.
Route Based on Open Intents
Add another Check contact attributes block:
Attribute: $.External.has_open_intents
If true, route based on intent type:
Check $.External.open_intent:
- "billing_issue" → Transfer to billing queue
- "technical_support" → Transfer to tech queue
- "escalation" → Transfer to senior agent queue
Example routing:
If open_intent = "billing_issue":
→ Set queue to "Billing Queue"
→ Set priority to "High"
→ Transfer to queue
Step 4: Display Context to Agents
Set Task Attributes
Before transferring to queue, add Set contact attributes block:
Attribute: customer_context
Value: Last issue: <$.External.last_issue>
Attribute: caller_confidence
Value: <$.External.confidence>
Attribute: customer_ref
Value: <$.External.customer_ref>
Show in Agent CCP
These attributes appear in the agent's Contact Control Panel (CCP).
Option 1: Default CCP
Attributes show in the contact attributes panel:
- customer_context
- caller_confidence
- customer_ref
Option 2: Custom CCP (Streams API)
Build custom UI to display context prominently:
// In your custom CCP
contact.getAttributes((err, attributes) => {
if (attributes.customer_context) {
displayContextPanel({
lastIssue: attributes.customer_context.value,
confidence: attributes.caller_confidence.value,
isReturning: attributes.caller_confidence.value >= 0.7
});
}
});
Screen Pop with Context
Use Streams API to trigger screen pop:
contact.onConnecting((contact) => {
const attrs = contact.getAttributes();
if (attrs.customer_ref && attrs.customer_ref.value) {
// Open CRM with customer record
window.open(`https://crm.example.com/customer/${attrs.customer_ref.value}`);
}
});
Step 5: Save Context After Call
Create Save Context Lambda
Create second Lambda function: StickyCallsSaveContext
import json
import os
from urllib import request, error
STICKY_CALLS_API_KEY = os.environ['STICKY_CALLS_API_KEY']
API_BASE_URL = 'https://api.stickycalls.com'
def lambda_handler(event, context):
"""
Save call context after call ends
Triggered from contact flow disconnect flow
"""
# Get call details from event
contact_id = event['Details']['ContactData']['ContactId']
customer_ref = event['Details']['ContactData']['Attributes'].get('customer_ref', '')
# Get conversation summary (from agent notes or IVR)
call_summary = event['Details']['ContactData']['Attributes'].get('call_summary', '')
intent = event['Details']['ContactData']['Attributes'].get('intent', 'general_inquiry')
intent_status = event['Details']['ContactData']['Attributes'].get('intent_status', 'resolved')
agent_notes = event['Details']['ContactData']['Attributes'].get('agent_notes', '')
try:
payload = {
'call_id': contact_id,
'customer_ref': customer_ref,
'intent': intent,
'intent_status': intent_status,
'variables': {
'last_issue': call_summary or agent_notes or 'Customer called for support',
'resolution': agent_notes,
'call_date': event['Details']['ContactData']['InitiationTimestamp']
}
}
api_url = f'{API_BASE_URL}/v1/calls/end'
headers = {
'Authorization': f'Bearer {STICKY_CALLS_API_KEY}',
'Content-Type': 'application/json'
}
req = request.Request(
api_url,
data=json.dumps(payload).encode('utf-8'),
headers=headers,
method='POST'
)
response = request.urlopen(req, timeout=3)
print('Context saved successfully')
return {
'statusCode': 200,
'body': json.dumps({'success': True})
}
except Exception as e:
print(f'Error saving context: {str(e)}')
# Don't fail the call if save fails
return {
'statusCode': 200,
'body': json.dumps({'success': False, 'error': str(e)})
}
Configure Disconnect Flow
- Create or edit your contact flow's disconnect flow
- Add Invoke AWS Lambda function block
- Select function:
StickyCallsSaveContext - This runs after every call ends
Capture Agent Notes
To save agent notes, use Contact Lens or custom integration:
// In your CCP customization
agent.onAfterCallWork(() => {
const notes = getAgentNotes();
// Set as contact attribute
contact.addConnection(endpoint, {
agent_notes: notes
});
});
Advanced Use Cases
Multi-Channel Consistency
Maintain context across phone, chat, and email:
# In Lambda, check for external customer ID
if customer_id:
payload['identity_hints']['external_ids'] = {
'customer_id': customer_id,
'email': customer_email # From your CRM
}
This ensures same context across all channels.
VIP Routing
Prioritize high-value customers:
If confidence >= 0.8 AND customer_ref starts with "vip_":
→ Set priority: Highest
→ Set queue: VIP Queue
→ Set working queue: Senior agents only
Callback Queue Priority
Give returning callers priority in callback queue:
If is_returning_caller = true AND has_open_intents = true:
→ Set callback priority: High
→ Estimated wait time: Reduce by 50%
Best Practices
1. Set Appropriate Timeouts
Lambda timeout: 5 seconds (allows API time to respond) Contact flow Lambda timeout: 5 seconds Always provide fallback: Never let API failure block calls
2. Handle Errors Gracefully
try:
# API call
response = call_api()
except Exception as e:
# Log error but continue call flow
print(f'Error: {e}')
return fallback_response() # Return default values
3. Optimize for Cost
Cache frequently called numbers (optional):
import boto3
dynamodb = boto3.resource('dynamodb')
cache_table = dynamodb.Table('StickyCallsCache')
# Check cache first
cached = cache_table.get_item(Key={'phone': phone_number})
if cached and is_fresh(cached['timestamp']):
return cached['data']
# If not cached, call API and cache result
data = call_sticky_calls_api()
cache_table.put_item(Item={
'phone': phone_number,
'data': data,
'timestamp': time.time(),
'ttl': time.time() + 300 # 5 min cache
})
4. Monitor Lambda Performance
Enable CloudWatch logging:
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.info(f'Identifying caller: {phone_number}')
logger.info(f'Confidence: {confidence}')
View logs in CloudWatch Logs → /aws/lambda/StickyCallsIdentifyCaller
5. Test Thoroughly
Test scenarios:
- First-time caller (confidence = 0)
- Returning caller (confidence >= 0.7)
- API timeout
- Invalid API key
- Network failure
Testing
Test Contact Flow
- Test Chat in contact flow editor
- Use Test settings → Add test phone number
- Trigger flow with test call
- Check logs in CloudWatch
Verify Lambda Execution
CloudWatch Logs will show:
START RequestId: abc-123
Identifying caller: +14155551234
API Response: {customer_ref: "cust_xyz", confidence: 0.92}
END RequestId: abc-123
Common Issues
Issue: Lambda times out
Solution: Increase timeout to 5-8 seconds, check API key
Issue: Attributes not showing in CCP
Solution: Verify Set contact attributes block is before queue transfer
Issue: Context not saved
Solution: Check disconnect flow has save Lambda, verify customer_ref is passed
Metrics & Impact
Real customer results from Amazon Connect + Sticky Calls:
| Metric | Before | After | Change |
|---|---|---|---|
| Average Handle Time | 9.2 min | 6.3 min | -32% |
| First Call Resolution | 64% | 91% | +27 pts |
| CSAT Score | 3.8/5 | 4.6/5 | +21% |
| Calls/Agent/Day | 28 | 35 | +25% |
| Repeat Calls (Same Issue) | 18% | 4% | -78% |
ROI Calculation:
- Contact center: 50 agents
- Cost per agent: $25/hour
- Time savings per call: 2.9 minutes
- Calls per day: 1,400
- Daily savings: 1,400 calls × 2.9 min / 60 = 67.7 hours
- Monthly savings: 67.7 hours × $25 × 22 days = $37,285/month
Customer testimonial:
"Sticky Calls transformed our Amazon Connect operation. Agents now see full customer history before answering, and our FCR jumped from 64% to 91%. Best ROI we've seen from any contact center tool."
— Operations Director, Healthcare Contact Center
Troubleshooting
Lambda Not Invoked
Check:
- Lambda added to Amazon Connect instance
- Contact flow has correct function selected
- IAM permissions allow Connect to invoke Lambda
Attributes Empty
Check:
- Lambda returns proper JSON structure
- External namespace used:
$.External.attribute_name - Timeout not exceeded
High API Costs
Solutions:
- Implement DynamoDB caching (5-minute TTL)
- Only call API for inbound calls (not internal transfers)
- Use confidence threshold to skip API for internal numbers
Next Steps
You've successfully integrated caller context into Amazon Connect! Here's what to explore next:
- Amazon Lex Integration - Add caller memory to voice bots
- Multi-Channel Use Case - Unify phone + chat + email context
- VIP Customer Experience - Premium routing patterns
- API Reference - Complete API documentation
- Reduce AHT Guide - Measure business impact
Need help? Contact support
Ready to start? Get your free API key →