Transfer a call¶
The bot chooses a safe route name. The deployment maps it to the protected phone or SIP destination.
When the selected preset enables transfer_call, the model sees
human-support—never the raw destination.
Resolve sensitive business context in the call's preparation hook and freeze only the minimum value the deployment transfer operation needs:
@assistant.prepare
async def prepare_call(call: Call) -> object:
account = await salesforce.resolve_verified_account(call.caller)
return {"salesforce_account_id": account.id}
Deployment-owned handoff¶
The default operation is the accepted LiveKit handoff's transfer_call, which
uses LiveKit SIP REFER. A deployment can supply an Assistant-level
transfer_operation when another control plane owns the handoff:
from vaani import Assistant, Call, TransferDisposition
async def complete_transfer(call: Call, destination: str) -> TransferDisposition:
# Call the deployment's control plane here. Keep credentials and provider
# payloads inside that deployment-owned integration.
account_id = call.state["salesforce_account_id"]
await ozonetel_transfer(destination, uui=account_id)
return TransferDisposition.COMPLETED
assistant = Assistant(
name="Support",
preset="basic-monolingual@1",
prompt="Help the caller.",
transfer_operation=complete_transfer,
)
When configured, this operation receives the resolved immutable Call and the
registered destination, then suppresses the default LiveKit transfer_call/SIP
REFER operation. A consumer may resolve sensitive business context once in
@assistant.prepare and return it in Call.state; the exact frozen state is
bound to this call's transfer closure. For example, the Birla deployment stores
the verified Salesforce AccountId as salesforce_account_id and passes it as
Ozonetel UUI. This is request-scoped data, not a preset-global value or a
fallback that can claim screen-pop success. Never put the raw AccountId/UUI in
logs, exceptions, Judge evidence, browser payloads, or transfer records.
Call.metadata still contains only the fields allowlisted by the selected
preset; it is immutable. Return TransferDisposition.COMPLETED only after the
deployment has completed the handoff; ACCEPTED records acceptance without
claiming completion. Omitting transfer_operation or setting it to None
preserves the current LiveKit REFER behavior exactly.
The deployment operation is not a provider client supplied by VAANI. Do not log
the destination, CTI values, credentials, request bodies, or exception details;
VAANI records only provider-neutral transfer evidence. The Birla
birla-prod-v42@14 preset allowlists the CTI correlation fields ucid, mucid,
did, and callerno for the deployment's preparation and business
logic. Other metadata remains dropped. Salesforce AccountId belongs in
consumer-owned state, not in preset-global configuration or arbitrary incoming
metadata.
Requirements¶
- Register the route before
assistant.check(). - Keep the destination in deployment configuration.
- Authorize the caller and transfer reason in bot-owned business logic.
- Regenerate the production lock after routes change.
- Test the actual SIP destination in the target environment.
Interpret the result¶
| Disposition | Meaning |
|---|---|
REJECTED_AFTER_END_REQUEST |
ending was already accepted before transfer admission |
FAILED |
handoff failed |
TIMED_OUT |
completion was not observed before the deadline |
ACCEPTED |
provider accepted the request; completion is not proven |
COMPLETED |
typed completion evidence was received |
Do not display ACCEPTED as completed. VAANI 1.0 supports blind transfer, not
warm consultation or conference transfer.
Next: production readiness.