Skip to main content

Generic IVR Integration

Universal REST API integration pattern that works with any IVR platform.


Overview

This guide provides a platform-agnostic integration pattern for Sticky Calls API. Use this if you're working with:

  • Custom IVR systems
  • Legacy platforms
  • Non-mainstream contact center software
  • Home-grown solutions
  • Any system that can make HTTP REST calls

Setup Time: 10-15 minutes Difficulty: Very Easy Prerequisites:

  • Ability to make HTTP POST requests from your IVR
  • Ability to parse JSON responses
  • Sticky Calls API key

Integration Pattern

1. Call Start - Identify Caller

When: At the beginning of every call Purpose: Identify returning callers and retrieve context

HTTP Request:

POST /v1/calls/start HTTP/1.1
Host: api.stickycalls.com
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
"call_id": "unique_call_identifier",
"identity_hints": {
"ani": "+14155551234",
"external_ids": {
"customer_id": "optional_crm_id"
}
}
}

HTTP Response (Returning Caller):

HTTP/1.1 200 OK
Content-Type: application/json

{
"call_id": "unique_call_identifier",
"customer_ref": "cust_a1b2c3d4",
"call_start": "2026-02-06T15:30:00.000Z",
"identity": {
"confidence": 0.95,
"level": "very_high",
"sources": ["ani:mobile", "external_id", "recency:1day"],
"recommendation": "reuse"
},
"open_intents": [
{
"intent": "follow_up_required",
"status": "open",
"attempt_count": 1
}
],
"variables": {
"last_issue": {
"value": "Customer called about billing issue. Refund processed.",
"source": null,
"ttl_seconds": 2592000
}
}
}

HTTP Response (New Caller):

HTTP/1.1 200 OK
Content-Type: application/json

{
"call_id": "unique_call_identifier",
"customer_ref": "cust_new123",
"call_start": "2026-02-06T15:30:00.000Z",
"identity": {
"confidence": 0.0,
"level": "low",
"sources": [],
"recommendation": "ignore"
},
"open_intents": [],
"variables": {}
}

2. Call End - Save Context

When: At the end of every call Purpose: Save conversation summary for future calls

HTTP Request:

POST /v1/calls/end HTTP/1.1
Host: api.stickycalls.com
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
"call_id": "same_call_identifier_as_start",
"customer_ref": "cust_a1b2c3d4",
"intent": "billing_inquiry",
"intent_status": "resolved",
"variables": {
"resolution": "Issued $50 refund for duplicate charge",
"refund_amount": "50.00",
"issue_type": "billing"
}
}

HTTP Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
"call_id": "same_call_identifier_as_start",
"customer_ref": "cust_a1b2c3d4",
"call_end": "2026-02-06T15:45:00.000Z"
}

Implementation Examples

Curl (Testing)

Start Call:

curl -X POST https://api.stickycalls.com/v1/calls/start \
-H "Authorization: Bearer sk_test_abc123" \
-H "Content-Type: application/json" \
-d '{
"call_id": "call_001",
"identity_hints": {
"ani": "+14155551234"
}
}'

End Call:

curl -X POST https://api.stickycalls.com/v1/calls/end \
-H "Authorization: Bearer sk_test_abc123" \
-H "Content-Type: application/json" \
-d '{
"call_id": "call_001",
"customer_ref": "cust_abc123",
"intent": "general_inquiry",
"intent_status": "resolved",
"variables": {
"resolution": "Issue resolved"
}
}'

Python

import requests

API_KEY = "sk_test_abc123"
BASE_URL = "https://api.stickycalls.com"

def identify_caller(call_id, ani):
response = requests.post(
f"{BASE_URL}/v1/calls/start",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={
"call_id": call_id,
"identity_hints": {
"ani": ani
}
}
)
return response.json()

def save_context(call_id, customer_ref, intent, intent_status, variables):
response = requests.post(
f"{BASE_URL}/v1/calls/end",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={
"call_id": call_id,
"customer_ref": customer_ref,
"intent": intent,
"intent_status": intent_status,
"variables": variables
}
)
return response.json()

# Usage
result = identify_caller("call_001", "+14155551234")
if result["identity"]["confidence"] >= 0.7:
last_issue = result["variables"].get("last_issue", {}).get("value", "")
print(f"Welcome back! Last issue: {last_issue}")
else:
print("Welcome! First time caller.")

# At end of call
save_context(
call_id="call_001",
customer_ref=result["customer_ref"],
intent="general_inquiry",
intent_status="resolved",
variables={"resolution": "Issue resolved successfully"}
)

Node.js

const axios = require('axios');

const API_KEY = 'sk_test_abc123';
const BASE_URL = 'https://api.stickycalls.com';

async function identifyCaller(callId, ani) {
const response = await axios.post(
`${BASE_URL}/v1/calls/start`,
{
call_id: callId,
identity_hints: {
ani: ani
}
},
{
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
}

async function saveContext(callId, customerRef, intent, intentStatus, variables) {
const response = await axios.post(
`${BASE_URL}/v1/calls/end`,
{
call_id: callId,
customer_ref: customerRef,
intent: intent,
intent_status: intentStatus,
variables: variables
},
{
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
}

// Usage
(async () => {
const result = await identifyCaller('call_001', '+14155551234');
if (result.identity.confidence >= 0.7) {
const lastIssue = result.variables.last_issue?.value || '';
console.log(`Welcome back! Last issue: ${lastIssue}`);
} else {
console.log('Welcome! First time caller.');
}

// At end of call
await saveContext(
'call_001',
result.customer_ref,
'general_inquiry',
'resolved',
{ resolution: 'Issue resolved successfully' }
);
})();

PHP

<?php
$apiKey = 'sk_test_abc123';
$baseUrl = 'https://api.stickycalls.com';

function identifyCaller($callId, $ani) {
global $apiKey, $baseUrl;

$ch = curl_init("$baseUrl/v1/calls/start");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $apiKey",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'call_id' => $callId,
'identity_hints' => [
'ani' => $ani
]
]));

$response = curl_exec($ch);
curl_close($ch);

return json_decode($response, true);
}

function saveContext($callId, $customerRef, $intent, $intentStatus, $variables) {
global $apiKey, $baseUrl;

$ch = curl_init("$baseUrl/v1/calls/end");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $apiKey",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'call_id' => $callId,
'customer_ref' => $customerRef,
'intent' => $intent,
'intent_status' => $intentStatus,
'variables' => $variables
]));

$response = curl_exec($ch);
curl_close($ch);

return json_decode($response, true);
}

// Usage
$result = identifyCaller('call_001', '+14155551234');
if ($result['identity']['confidence'] >= 0.7) {
$lastIssue = $result['variables']['last_issue']['value'] ?? '';
echo "Welcome back! Last issue: " . $lastIssue;
} else {
echo "Welcome! First time caller.";
}

// At end of call
saveContext(
'call_001',
$result['customer_ref'],
'general_inquiry',
'resolved',
['resolution' => 'Issue resolved successfully']
);
?>

Decision Logic

Basic Personalization

IF identity.confidence >= 0.7:
Play personalized greeting
Show context to agent
Route to specialized queue
ELSE:
Play standard greeting
Standard routing

Confidence-Based Actions

IF identity.confidence >= 0.9:
High confidence - Full personalization
ELSE IF identity.confidence >= 0.7:
Medium confidence - Ask for verification
ELSE IF identity.confidence >= 0.3:
Low confidence - Standard flow with note
ELSE:
New caller - Standard onboarding

Open Intent Routing

FOR EACH intent IN open_intents:
IF intent.intent = "billing_issue" AND intent.status = "open":
Route to: Billing queue
Priority: High
ELSE IF intent.intent = "technical_support" AND intent.status = "open":
Route to: Technical queue
ELSE IF intent.intent = "escalation" AND intent.status = "open":
Route to: Senior agents
Priority: Urgent

IF no open intents found:
Route to: General queue

Error Handling

HTTP Status Codes

CodeMeaningAction
200SuccessParse response, continue flow
400Bad RequestLog error, use standard flow
401UnauthorizedCheck API key, alert admin
402Payment RequiredAlert admin (low credits)
429Rate LimitRetry after 1 second
500Server ErrorLog, use standard flow

Timeout Handling

Set timeout: 5 seconds

ON TIMEOUT:
Log: "API timeout - continuing with standard flow"
Set: match_found = false
Continue: Standard greeting

Retry Logic

max_retries = 2
retry_delay = 1 second

FOR attempt IN 1 TO max_retries:
TRY:
response = call_api()
BREAK
CATCH timeout_error:
IF attempt < max_retries:
WAIT retry_delay
ELSE:
Use standard flow

Best Practices

1. Always Include call_id

Use a unique identifier for each call:

call_id = f"{platform_prefix}_{timestamp}_{uuid}"
Example: "pbx_20250128_abc123"

2. Normalize Phone Numbers

Format ANI consistently:

+14155551234  ✅ E.164 format (recommended)
14155551234 ✅ Also works
4155551234 ❌ Missing country code

3. Set Appropriate Timeouts

Connection timeout: 3 seconds
Read timeout: 5 seconds
Total timeout: 8 seconds max

4. Handle Failures Gracefully

Never block the call flow:

TRY:
result = identify_caller()
CATCH any_error:
result = {"match_found": false}
FINALLY:
Continue with call flow

5. Save Rich Context

Don't just save "resolved" - include details:

❌ variables: { "status": "resolved" }
✅ variables: {
"resolution": "Billing issue resolved. Refunded $50 for duplicate charge on 1/15.",
"refund_amount": "50.00",
"charge_date": "2026-01-15"
}

6. Use External IDs

Include CRM IDs when available:

{
"call_id": "call_001",
"identity_hints": {
"ani": "+14155551234",
"external_ids": {
"crm_customer_id": "CUST-12345",
"account_number": "ACC-67890"
}
}
}

Testing

1. Test API Connectivity

curl -v https://api.stickycalls.com/v1/health
# Expected: {"status":"healthy"}

2. Test Authentication

curl -X POST https://api.stickycalls.com/v1/calls/start \
-H "Authorization: Bearer sk_test_abc123" \
-H "Content-Type: application/json" \
-d '{"call_id":"test","identity_hints":{"ani":"+14155551234"}}'

# Expected: 200 OK with JSON response

3. Test Match Logic

# First call (no match expected)
curl -X POST https://api.stickycalls.com/v1/calls/start \
-H "Authorization: Bearer sk_test_abc123" \
-H "Content-Type: application/json" \
-d '{"call_id":"test_001","identity_hints":{"ani":"+14155559999"}}'
# Expected: identity.confidence = 0.0

# Save context (note: use customer_ref from response above)
curl -X POST https://api.stickycalls.com/v1/calls/end \
-H "Authorization: Bearer sk_test_abc123" \
-H "Content-Type: application/json" \
-d '{
"call_id":"test_001",
"customer_ref":"cust_abc123",
"intent":"test_intent",
"intent_status":"resolved",
"variables":{"test_note":"Test context"}
}'

# Second call (match expected)
curl -X POST https://api.stickycalls.com/v1/calls/start \
-H "Authorization: Bearer sk_test_abc123" \
-H "Content-Type: application/json" \
-d '{"call_id":"test_002","identity_hints":{"ani":"+14155559999"}}'
# Expected: identity.confidence >= 0.5, variables.test_note.value = "Test context"

Monitoring

Key Metrics to Track

  1. API Success Rate

    • Target: > 99.5%
    • Alert if < 95%
  2. Average Response Time

    • Target: < 200ms
    • Alert if > 1000ms
  3. Match Rate

    • Track: Percentage of calls with identity.confidence >= 0.7
    • Optimize: Aim for 20-40% (depends on call patterns)
  4. Credit Usage

    • Monitor: Daily/weekly credit consumption
    • Alert: When < 20% remaining

Dashboard Integration

View all metrics in your Sticky Calls dashboard:


Troubleshooting

API Call Fails

Check:

  1. API key format: Bearer sk_test_... or Bearer sk_prod_...
  2. URL correct: https://api.stickycalls.com
  3. Content-Type header: application/json
  4. Request body is valid JSON

No Match When Expected

Check:

  1. ANI format consistent between start/end calls
  2. call_id used correctly
  3. Context was actually saved (check logs)
  4. Not exceeding 90-day TTL

Credits Depleted

Actions:

  1. Purchase more credits at https://stickycalls.com/dashboard/billing
  2. Check for unexpected usage patterns
  3. Verify you're not charging for matches (should be free)

Next Steps


Ready to integrate? Sign up for free →