Recipe: Caller ID validation
Many voice agents need to confirm who they’re speaking to before sharing account data, processing requests, or completing transactions. This recipe shows how to collect an identifier, verify it against your backend, and gate the rest of the conversation on the result.
When to use this
Use this pattern when:
- The agent handles personal account data (balances, orders, medical records)
- Compliance or security policy requires identity verification before proceeding
- You want to personalize the conversation based on confirmed identity
The complete pattern
Step 1 – Collect the identifier
Step prompt: “Ask the caller for their date of birth in DD/MM/YYYY format. Once they provide it, call collect_identifier.”
Step 2 – Verify against the backend
Full flow
Using verified identity downstream
After verification, other functions can check conv.state.get("identity_verified") before returning sensitive data:
Use a consistent identity_verified flag pattern across all functions that touch sensitive data. This makes it easy to audit which functions are gated and which are not.
Key decisions
Why format-check before hitting the API?
A format check in collect_identifier catches obvious transcription errors (missing digits, wrong separator) before you make an API call that will fail anyway. It also gives the caller a more specific error message — “That doesn’t look like a valid date” rather than a generic “verification failed.”
Why store caller_name after verification?
Storing the verified caller’s name in conv.state lets the agent personalise subsequent responses — “Great, I can see your account, Aaron” — without making a second API call. Only store what you’ll use.
Why not use utterance for the failure messages?
For retry messages, returning content lets the LLM rephrase the request naturally (“Sorry, that didn’t match — could you try your date of birth again?”). Using a hard-coded utterance would make every retry sound identical.
Check your understanding
A function that returns account balance checks conv.state.get('identity_verified') before proceeding. Why not rely on the flow to guarantee this?
A function that returns account balance checks conv.state.get('identity_verified') before proceeding. Why not rely on the flow to guarantee this?
- Flows don’t have access to conv.state
- It’s a defensive guard — functions can be called outside of flows, and explicit checks prevent accidental data exposure
- The LLM can call any function at any time, ignoring flow step order
- It makes the function faster by skipping the API call
Answer: It’s a defensive guard — functions can be called outside of flows, and explicit checks prevent accidental data exposure
Functions can be referenced in the Behavior field or topics, not just flows. A function that returns sensitive data should verify the precondition itself rather than trusting that it will only ever be called from a verified flow step.

