October 31, 2026 · 7 min read

Give your AI agent instant context — pass caller metadata at the SIP layer, not after

Your AI agent gets the call. It doesn't know who it is. The classic fix is an API round-trip the moment the call connects, which is slow and brittle. Here's a cleaner pattern: attach caller context — customer tier, agent ID, session — as SIP metadata, so the AI knows everything from frame zero.

voice AItrunk headersVoyloMLarchitecture

Here's the pattern we see most often. A phone call lands. The caller is a known customer. Your AI agent needs to greet them by name, know their plan tier, pull up their last support ticket, and remember what the human agent said when they escalated two weeks ago.

The default way to do this is to look it up afterthe AI picks up the call: the vendor hits your backend's webhook on connect, you query by caller-ID, you return context as JSON, the AI eventually sees it and loads it into prompt context. That round-trip is slow (your webhook has to resolve before the AI's greeting is generated), brittle (what if your webhook is down for 30 seconds?), and ambiguous (who is +97150-whatever if that number isn't in your CRM?).

There's a better pattern: attach the context to the call itself, at the signalling layer, before the AI session is even opened. Voylo lets you do this two ways — statically on every call to a trunk, or dynamically per-call from a VoyloML flow. This post walks through both and when to use each.

Static trunk headers: for context that doesn't change per call

Open any inbound trunk → Custom SIP headers. Add key-value pairs. They're stamped on every outgoing INVITE from Voylo to the trunk's destinations — your AI vendor's inbound SBC. Names must start withX-so a typo can't overwrite a protocol header (From, Via, Contact) and corrupt call setup.

Trunk-level configuration (one time, in the UI)
X-Agent-Id:    agent_xj92kqp7
X-Api-Key:     your-elevenlabs-webhook-token
X-Env:         production

Your AI vendor receives these on every INVITE. In Vapi they're surfaced in the call.sip.headers object; in Retell they land in the webhook payload under sip_headers; ElevenLabs maps them into agent context automatically if you've subscribed the trunk to the right agent. For your own backend, they're just normal SIP headers on the INVITE — read them in whatever way your SBC gives you access.

Use this for context that's identical across every call: environment tags, authentication tokens for your AI vendor, agent IDs, routing hints. Set once, applied forever.

Dynamic headers: context that depends on who is calling

Static is enough for about half of real use cases. The other half needs per-call context — the caller's customer tier, their current support ticket, the agent they spoke with last. That context lives in your database, not in a trunk config.

The VoyloML pattern: fetch the context via a <Request> from your backend before dialing the AI vendor, bind the response fields into variables, then attach them as per-call <Header> children on the <Dial>.

Application XML — fetch, then dial with context
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <!-- 1. Resolve who's calling against YOUR database -->
  <Request url="https://your-api.example.com/call-context"
           resultVar="ctx">
    <Header name="Authorization" value="Bearer YOUR_API_KEY"/>
    <Param name="caller" value="{{From}}"/>
    <Param name="did"    value="{{To}}"/>
    <Var name="customerId"   path="customer.id"/>
    <Var name="customerTier" path="customer.tier"/>
    <Var name="agentId"      path="assigned_agent"/>
    <Var name="language"     path="customer.preferred_language"/>
    <Var name="sessionId"    path="session_id"/>
  </Request>

  <!-- 2. Dial the AI, stamping the fetched context onto the INVITE -->
  <Dial inboundTrunkId="trunk-abc-123" timeout="30">
    <Header name="X-Customer-Id"   value="{{customerId}}"/>
    <Header name="X-Customer-Tier" value="{{customerTier}}"/>
    <Header name="X-Agent-Id"      value="{{agentId}}"/>
    <Header name="X-Language"      value="{{language}}"/>
    <Header name="X-Session-Id"    value="{{sessionId}}"/>
  </Dial>
</Response>

Flow of execution:

  1. Call lands. {{From}}is the caller's E.164, {{To}} is the DID they dialed.
  2. Voylo issues an HTTP POST to your call-contextendpoint with those fields. Your backend does whatever lookup makes sense — match the caller against your customer DB, pull their current state, decide which agent they're routed to. Returns a JSON blob.
  3. Voylo binds JSON paths into named variables (customerTier, agentId, etc.) from the response.
  4. Voylo dials the AI vendor via the trunk. The <Header> children have {{var}} substitutions resolved at send time; the AI vendor receives the actual values on the INVITE.
  5. The AI's greeting is generated with the context already in prompt — "Hi, Ahmed, I see you're on the Pro plan and your last ticket was about SSO." No follow-up webhook needed to resolve identity.

Merge rules: static + dynamic

Static trunk headers and dynamic <Dial> children compose. The merged set applies on every INVITE:

  • Both sets are included.
  • Same header name on both sides → dynamic value wins. Useful for overrides — your trunk has a static X-Env=production but a specific flow needs to tag calls as X-Env=staging for a test run.
  • Max 20 effective headers per call. If your context dictionary is bigger, pass an opaque session ID header and have the AI fetch the rest from your backend by that ID — which gets you back to the two-call-pattern but with a stable key and no caller-ID matching ambiguity.

What context to send (and what not to)

Rough guide:

  • Send: identifiers (customer ID, session ID, ticket ID), coarse routing hints (tier, language, region), short strings (< 100 chars). SIP headers are not meant for payloads.
  • Don't send: conversation history, long customer notes, embeddings, anything that looks like a JSON blob. SIP infrastructure has header-length limits that vary per hop — your call setup can hard-fail against a cheap carrier that caps total header bytes at 2KB. Keep headers short; pass session IDs and let the AI vendor fetch rich context on its own.
  • PII sensitivity:headers travel in cleartext if the SIP leg isn't TLS. Voylo's outbound to major vendors is TLS by default, but confirm per vendor. Avoid putting raw PII (full names, DOBs) in headers — use IDs your AI vendor can map back.

Why this is better than webhook-on-connect

  1. Faster first-token latency. The AI already has context in its system prompt when it starts generating the greeting. No pause while a webhook resolves. Call feels instant.
  2. Robust to transient webhook failures. Your <Request> hits your backend at call setup. If it fails, VoyloML gives you clean error handling (<Switch on="RequestStatus">) — you can play an apology, route to a fallback, log and carry on with no context. A webhook-on-connect failure at the AI vendor is black-boxed; you often don't find out until a user complains.
  3. No ambiguity on unknown callers.Your backend tells Voylo definitively who this is before the AI is contacted. The AI can't confuse itself about the caller because it never had to guess.
  4. Portable across AI vendors.Switching from Vapi to Retell doesn't require rewriting the context flow — same SIP headers, same JSON on the receiving end, just a different trunk target.
For an in-depth look at the SIP header mechanics, including the merge order and the predial handoff, see the VoyloML reference. If you're coming from Twilio, the mental model is similar to their SIP-X-Headers parameter on <Dial><Sip> — same idea, cleaner syntax, no third-party domain-config step.

The one-line summary

If your AI needs to know anything about the caller beyond their phone number, send it as a SIP header, not as a webhook response after the call connects.It's faster, more reliable, and vendor-portable. Static for constants, dynamic (via <Request> then <Dial><Header>) for per-caller context. Both work. Both are already in Voylo.

Try this on Voylo

Free tier covers everything in this post. No credit card.