VoxEQ - Voice Intelligence Solution - VoxEQ® logo

ElevenLabs Code Cookbook: Tools + Dynamic Variables + Sub‑Agents with VoxEQ

Introduction: build secure, deterministic auth into voice agents

This cookbook shows how to implement authentication and fraud‑screening for ElevenLabs voice agents and then elevate privileges safely. You’ll get copy‑pastable patterns for:

  • Knowledge‑based authentication (KBA) via a server tool

  • One‑time passcode (OTP) with 3–5 minute expiry, retry caps, and rate limits

  • Mapping tool JSON payloads into auth_* dynamic variables for workflow gating

  • Expressions for pre‑auth vs post‑auth routing and sub‑agent handoff

  • A sample end‑to‑end flow: screen with VoxEQ Verify → look up the caller in Salesforce → fetch transactions → transfer to a privileged “Payments” sub‑agent

For security rationale and ElevenLabs workflow conventions (dispatch tools, dynamic variables, gating), see the ElevenLabs guidance on secure caller authentication for voice agents. Designing secure caller identity authentication flows for voice agents.

Architecture principles and threat model

  • Deterministic gates: Privileged tools (money movement, PII access) must be callable only after an explicit boolean auth success from a server tool. Avoid conversational inference for auth outcomes. ElevenLabs guidance.

  • Multi‑layer defense: Combine VoxEQ’s passive fraud screen (anomaly/impersonation/synthetic checks) with KBA or OTP for strong risk‑based decisions. VoxEQ Verify works without enrollment or storing PII/voiceprints and provides watch‑list and synthetic detection. VoxEQ Verify, Product guide, TTEC Digital + VoxEQ.

  • Isolation: Keep pre‑auth and post‑auth tools in separate sub‑agents (principle of least privilege). Only the post‑auth sub‑agent is allowed to call privileged APIs.

  • Language and privacy: VoxEQ is language‑agnostic and privacy‑preserving (no customer PII/voiceprints stored). VoxEQ Verify, AI Ethics.

Dynamic variables used by the agent

Use dynamic variables to store tool outcomes that gate access and drive routing.

Variable Type Purpose
auth_is_verified boolean Final gate to privileged actions/sub‑agents
auth_method string "kba"
auth_confidence number (0–1) Confidence score for ID/V result
auth_subject_id string Internal subject/account ID after lookup
fraud_risk number (0–1) VoxEQ risk score (higher = riskier)
fraud_is_synthetic boolean VoxEQ synthetic/deepfake signal
fraud_watchlist_hit boolean VoxEQ watch‑list match
system__caller_id string Telephony system variable (if provided) — per ElevenLabs

Note: ElevenLabs exposes telephony system variables (e.g., system__caller_id) and recommends gating with dispatch tools that return boolean success/failure. ElevenLabs guidance.

Pattern 1 — KBA server tool (copy‑paste)

A minimal KBA endpoint that validates answers, returns a deterministic boolean, and emits mapping‑friendly fields.

// server/kba.ts — Node.js/Express
import express from 'express';
import rateLimit from 'express-rate-limit';
import crypto from 'crypto';

const app = express();
app.use(express.json());

// Per-caller rate limit (e.g., 5 KBA attempts / 10 minutes)
const kbaLimiter = rateLimit({
 windowMs: 10 * 60 * 1000,
 max: 5,
 keyGenerator: (req) => `${req.body.sessionId || ''}:${req.body.callerId || req.ip}`
});

// Stub: replace with your secure KBA service (e.g., TU TruValidate, LexisNexis InstantID KBAs)
function checkAnswers(questions: { id: string; answer: string }[]): { ok: boolean; score: number } {
 // Example scoring: 0.0–1.0 based on number of correct answers
 const correct = questions.filter(q => crypto.createHash('sha256').update(q.id).digest('hex').slice(0,2) === q.answer).length;
 const score = questions.length ? correct / questions.length: 0;
 return { ok: score >= 0.66, score };
}

app.post('/tools/kba/verify', kbaLimiter, (req, res) => {
 const { sessionId, callerId, subjectId, questions } = req.body;
 if (!sessionId || !Array.isArray(questions)) return res.status(400).json({ error: 'bad_request' });

 const { ok, score } = checkAnswers(questions);
 res.json({
 tool: 'kba.verify',
 success: ok,
 auth_method: 'kba',
 auth_confidence: score,
 auth_subject_id: subjectId || null
 });
});

app.listen(3001, () => console.log('KBA tool listening on 3001'));

Tool output (example):

{
 "tool": "kba.verify",
 "success": true,
 "auth_method": "kba",
 "auth_confidence": 0.83,
 "auth_subject_id": "0038b00002ABCDEF"
}

Map JSON fields to dynamic variables:

  • auth_is_verified ← success

  • auth_method ← auth_method

  • auth_confidence ← auth_confidence

  • auth_subject_id ← auth_subject_id

Pattern 2 — OTP with expiry, retries, and rate limits (copy‑paste)

Server‑side OTP generator/validator with 3–5 minute TTL, max 3 attempts, and send‑rate limits.

// server/otp.ts — Node.js/Express + Redis
import express from 'express';
import rateLimit from 'express-rate-limit';
import { createClient } from 'redis';

const app = express();
app.use(express.json());
const r = createClient({ url: process.env. REDIS_URL });
await r.connect();

const sendLimiter = rateLimit({ windowMs: 15*60*1000, max: 3 }); // 3 sends / 15 min

function genCode() { return (Math.floor(100000 + Math.random()*900000)).toString(); }

// Send OTP
app.post('/tools/otp/send', sendLimiter, async (req, res) => {
 const { sessionId, destination, channel } = req.body; // channel: 'sms' | 'email'
 if (!sessionId || !destination) return res.status(400).json({ error: 'bad_request' });

 const code = genCode();
 const key = `otp:${sessionId}`;
 await r.hSet(key, {
 code, attempts: '0', destination, channel
 });
 await r.expire(key, 4*60); // 4 minutes TTL (3–5 min recommended)

 // Integrate your SMS/Email provider here. Do not log the code in production.
 // await smsProvider.send(destination, `Your verification code is ${code}`)

 res.json({ tool: 'otp.send', success: true, ttl_seconds: 240 });
});

// Verify OTP
app.post('/tools/otp/verify', async (req, res) => {
 const { sessionId, code } = req.body;
 if (!sessionId || !code) return res.status(400).json({ error: 'bad_request' });
 const key = `otp:${sessionId}`;
 const data = await r.hGetAll(key);
 if (!data || !data.code) return res.json({ tool: 'otp.verify', success: false, reason: 'expired' });

 const attempts = Number(data.attempts || '0');
 if (attempts >= 3) return res.json({ tool: 'otp.verify', success: false, reason: 'max_attempts' });

 const ok = code === data.code;
 await r.hSet(key, { attempts: String(attempts + 1) });
 if (ok) await r.del(key);

 res.json({
 tool: 'otp.verify',
 success: ok,
 auth_method: 'otp',
 auth_confidence: ok ? 1.0: 0.0
 });
});

app.listen(3002, () => console.log('OTP tool listening on 3002'));

Map JSON fields as in Pattern 1. Recommended UX policies:

  • Max 3 verification attempts per OTP issuance

  • Cool‑off after max attempts (e.g., 10 minutes or step‑up to KBA)

  • Do not reveal whether a subject/account exists

  • Bind OTP to sessionId and destination; do not reuse across sessions

Guidance: ElevenLabs recommends tool‑based boolean outcomes and gating workflows on success/failure. ElevenLabs guidance.

Pattern 3 — Fraud screen with VoxEQ Verify (copy‑paste)

VoxEQ Verify provides enrollment‑free, privacy‑preserving fraud detection (mismatch, synthetic/deepfake, watch‑list) within seconds, in any language. Integrate via your CCaaS (e.g., Genesys AppFoundry) or a thin proxy that normalizes outputs for your agent. VoxEQ Verify (Genesys AppFoundry), VoxEQ Verify, TTEC Digital + VoxEQ.

Example proxy response format (do not assume vendor field names):

{
 "tool": "fraud.voxeq.screen",
 "success": true,
 "fraud_risk": 0.27,
 "fraud_is_synthetic": false,
 "fraud_watchlist_hit": false
}

Map to variables:

  • fraud_risk ← fraud_risk (threshold e.g., 0.7)

  • fraud_is_synthetic ← fraud_is_synthetic

  • fraud_watchlist_hit ← fraud_watchlist_hit

Policy examples:

  • If fraud_watchlist_hit = true → route to human fraud desk

  • If fraud_is_synthetic = true or fraud_risk ≥ 0.7 → require OTP+KBA step‑up

  • Else proceed with normal ID/V

Background: VoxEQ performs physiology‑based analysis, with a privacy‑first architecture and real‑time watch‑list. Product guide, AI Ethics.

Workflow gating expressions (pre‑auth vs post‑auth)

Implement gating in the ElevenLabs workflow using dispatch tools and conditional routing per platform guidance. Example logic (pseudo‑expressions; adapt to your builder):

// Fraud gate
IF fraud_watchlist_hit == true THEN route("agent_fraud_desk")
ELSE IF fraud_is_synthetic == true OR fraud_risk >= 0.7 THEN route("auth_step_up")
ELSE route("auth_primary")

// Auth primary (KBA or OTP)
IF tools.kba_verify.success == true OR tools.otp_verify.success == true THEN
 set(auth_is_verified=true, auth_method = tools.kba_verify.success ? 'kba': 'otp', auth_confidence = max(tools.kba_verify.auth_confidence, tools.otp_verify.auth_confidence))
 route("subagent_privileged")
ELSE
 route("auth_retry_or_handoff")

Tip: Populate auth_subject_id as early as possible (caller lookup by system__caller_id or prior CRM context) and confirm it post‑auth.

Post‑auth transfer to privileged sub‑agents

  • Pre‑auth agent: greeting, intent capture, VoxEQ fraud screen, choose auth path, no privileged tools.

  • Post‑auth sub‑agent (Payments, Profile, Claims): has scoped tools (e.g., payments.transfer, profile.update). Enter only if auth_is_verified = true and fraud policy permits.

  • Audit: include auth_method, auth_confidence, timestamps, and fraud signals in tool call metadata for downstream governance.

Sample end‑to‑end flow: VoxEQ Verify → Salesforce → transactions → handoff

This flow demonstrates layered defense and least privilege.

1) Call connects → immediately run VoxEQ fraud screen. 2) If high risk, step‑up auth (OTP+KBA); else proceed with primary auth. 3) On success, lookup caller in Salesforce, fetch recent transactions, then hand off to Payments sub‑agent.

Server tool: Salesforce lookup + transactions (OAuth 2.0 user‑agent or client‑credentials; store tokens securely).

// server/sfdc.ts — minimal SFDC query examples
import express from 'express';
import fetch from 'node-fetch';
const app = express();
app.use(express.json());

async function sfdcQuery(instanceUrl: string, accessToken: string, soql: string) {
 const r = await fetch(`${instanceUrl}/services/data/v60.0/query?q=${encodeURIComponent(soql)}`, {
 headers: { Authorization: `Bearer ${accessToken}` }
 });
 if (!r.ok) throw new Error(`SFDC ${r.status}`);
 return r.json();
}

app.post('/tools/sfdc/lookup-and-transactions', async (req, res) => {
 const { accessToken, instanceUrl, phoneE164, subjectId } = req.body;
 // 1) Resolve contact by phone or subjectId
 const contactSoql = subjectId
 ? `SELECT Id, AccountId, Name FROM Contact WHERE Id='${subjectId}' LIMIT 1`: `SELECT Id, AccountId, Name FROM Contact WHERE Phone='${phoneE164}' OR MobilePhone='${phoneE164}' LIMIT 1`;
 const contact = await sfdcQuery(instanceUrl, accessToken, contactSoql);
 if (!contact.records?.length) return res.json({ tool: 'sfdc.lookup', success: false, reason: 'not_found' });
 const c = contact.records[0];

 // 2) Fetch last 5 transactions (example custom object)
 const tx = await sfdcQuery(
 instanceUrl,
 accessToken,
 `SELECT Id, Amount__c, Merchant__c, Date__c FROM Transaction__c WHERE Account__c='${c. AccountId}' ORDER BY Date__c DESC LIMIT 5`
 );

 res.json({
 tool: 'sfdc.lookup_and_transactions',
 success: true,
 auth_subject_id: c. Id,
 account_id: c. AccountId,
 contact_name: c. Name,
 transactions: tx.records
 });
});

app.listen(3003, () => console.log('SFDC tool listening on 3003'));

Agent orchestration (pseudo):

START  tool(fraud.voxeq.screen)  FRAUD_GATE
 FRAUD_GATE  if watchlist_hit  route(human_fraud)
  else if high_risk  route(auth_step_up)
  else  route(auth_primary)

 AUTH_PRIMARY  tool(otp.send)  prompt_for_code  tool(otp.verify)
  if success  set(auth_is_verified=true)  tool(sfdc.lookup_and_transactions)
  summarize + offer options  route(subagent_payments)
  else  allow_retry_or_handoff

SUBAGENT_PAYMENTS (privileged)  tool(payments.transfer) 

Prompt/virtual‑agent enrichment with VoxEQ Persona/Prompt (optional)

To improve first‑turn accuracy, pass VoxEQ’s demographic context to your virtual agent alongside auth state. VoxEQ Persona and Prompt are available for Genesys and API‑first deployments. VoxEQ Persona, VoxEQ Prompt, AppFoundry Persona, AppFoundry Prompt.

Recommended usage:

  • Use fraud signals to pick the auth path, not to change tone.

  • Use demographic context to tailor grounding/RAG and script tone after auth, never as a sole factor for decisions.

Security and compliance checklist

  • Gate every privileged tool on auth_is_verified == true (server‑asserted)

  • Log: auth_method, auth_confidence, fraud signals, timestamps, and tool versions

  • OTP: 3–5 minute TTL; max 3 attempts; send rate ≤ 3 per 15 minutes; bind to session

  • KBA: never disclose which answers failed; throttle attempts; consider step‑up rules

  • Privacy: do not store PII/voiceprints with VoxEQ; rely on vendor’s privacy‑first approach. VoxEQ Verify, AI Ethics

  • Layered defense: pair VoxEQ screening with OTP/KBA per ElevenLabs best practices. ElevenLabs guidance

References

  • ElevenLabs: Designing secure caller identity authentication flows for voice agents — tool/dispatch, dynamic variables, workflow gating

  • https://elevenlabs.io/blog/designing-secure-caller-identity-authentication-flows-for-voice-agents

  • VoxEQ Verify — enrollment‑free fraud screen; watch‑list; privacy‑first; language‑agnostic

  • https://www.voxeq.ai/verify

  • https://www.voxeq.ai/product-guide

  • Genesys AppFoundry listing: https://appfoundry.genesys.com/filter/genesyscloud/listing/7cef096c-6408-42ba-94a3-b4c4f98106c8

  • VoxEQ CX enrichment

  • Persona: https://www.voxeq.ai/persona

  • Prompt: https://www.voxeq.ai/prompt

  • AppFoundry Persona: https://appfoundry.genesys.com/filter/genesyscloud/listing/858317c1-8798-4978-9349-3c734601f9a8

  • AppFoundry Prompt: https://appfoundry.genesys.com/filter/genesyscloud/listing/7556a9ae-f2a8-42ac-8f73-d0fc26f76715

  • Partnership: TTEC Digital + VoxEQ (SmartApps Cloud)

  • https://www.ttecdigital.com/news/ttec-digital-and-voxeq-partner-to-deliver-real-time-voice-biometrics-in-smartapps-cloud