Adding commercial guardrails to Retell AI agents
You add commercial guardrails to Retell AI agents by routing every quote, discount, refund, and commitment through a Retell custom function, and having that function's webhook handler post the intended words to EvalLayer's authorization API before returning them for the agent to speak. The agent never voices a price or promise that has not passed policy. Everything else in the conversation, greetings, questions, small talk, flows untouched.
Why voice agents concentrate commercial risk
Retell agents answer phones for real businesses: booking appointments, qualifying leads, handling support and sales calls. Those are exactly the conversations where money comes up, and voice has no draft folder. A chat agent's bad reply sits in a transcript someone might catch; a spoken commitment is heard by the customer the instant it is generated. If the agent tells a caller "we can do 30 percent off if you book today," the business just made an offer, in its own name, with a recording to prove it.
Retell's platform strength is conversation quality: low-latency speech, interruption handling, conversation flow states, and post-call analysis. Its webhooks fire on call_started, call_ended, and call_analyzed, which is excellent audit infrastructure but arrives after the caller hung up. Nothing in the stack evaluates whether a specific spoken offer complies with your commercial policy before it is spoken. That is the layer you add.
The interception point: custom functions
Retell's integration surface for mid-call logic is the custom function. When the agent decides to invoke one, Retell sends a webhook to your endpoint during the live call, and the string your server returns is what the agent says next. That request-response loop is a natural authorization gate.
The design has two parts. First, instruct the agent (in its prompt and function descriptions) that any price, discount, refund, or commitment must come from the function, never from its own head. Second, in the function handler, draft the response, check it, and return only what passed:
// Express handler for your Retell custom function webhook.
// Adapt field names to the payload Retell sends your function.
app.post('/retell/quote', async function (req, res) {
const draft = buildQuoteText(req.body.args);
const check = await fetch('https://api.evallayer.ai/authorize', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.EVALLAYER_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
action_type: 'send_quote',
content: draft,
context: { channel: 'voice', call_id: req.body.call.call_id },
policy: {
max_discount_pct: 10,
commitment_authority_usd: 500,
forbidden_commitments: ['delivery_date', 'refund_promise'],
approved_claims: [],
required_disclaimers: [],
blocked_topics: ['competitor_pricing']
}
})
});
const result = await check.json();
if (result.decision === 'allow') {
return res.json({ response: draft });
}
if (result.decision === 'rewrite') {
return res.json({ response: result.approved_rewrite });
}
if (result.decision === 'require_approval') {
queueForHuman(draft, result.authorization_id);
return res.json({ response: 'That one needs a quick sign-off on our side. We will call you right back to confirm.' });
}
logBlocked(result.authorization_id, result.violations);
return res.json({ response: 'I am not able to offer that on this call, but let me connect you with someone who can look into it.' });
});
Handling the four decisions on a live call
- allow: return the draft unchanged. The agent speaks it.
- rewrite: return
approved_rewriteinstead. The caller hears the compliant version, for example the quote with the 10 percent cap applied and the delivery guarantee softened to an estimate. - require_approval: the offer exceeded authority. Queue it with its
authorization_idfor a human, and have the agent promise a callback rather than the deal. This is the honest voice equivalent of "let me check with my manager." - block: never speak the draft. Return a graceful redirect and log the
authorization_idwith the violations.
Latency: check commitments, not utterances
Do not authorize every sentence the agent says; that would add a pause to small talk for no benefit. Check at the commitment moment, which in Retell's architecture is precisely when a custom function fires. Policies that are purely deterministic (caps, spend authority, required text) resolve in milliseconds and are unnoticeable. Semantic checks, such as whether a sentence constitutes a delivery guarantee, take one to two seconds. Retell gives your webhook up to ten seconds to respond, so both fit; for semantic checks, give the agent a filler line like "let me pull that up for you" so the pause sounds human.
What to log
Store the authorization_id from every check alongside the Retell call ID. When the call_analyzed webhook arrives with the transcript and recording, you can join the two: here is what the agent said, and here is the policy decision that authorized it. If a caller later disputes a quote, that pair is your answer. The risk_score and processing_time_ms fields are useful for monitoring drift in what your agent attempts over time.
Try it before you wire it
The fastest way to see the loop is the live demo, which runs a quote with planted violations through a sample policy. To test from your own handler, POST to /demo/authorize (three free checks per day, no key required), then swap in a Bearer sk_ key for production. The mechanics of the decision model are covered in the authorization guide, and policy design covers what belongs in the policy object for a voice deployment.