late-glass-71191
12/28/2025, 10:02 PMtall-airline-16756
12/28/2025, 10:02 PMtall-airline-16756
12/28/2025, 10:02 PMsip_trunk_id when creating SIP participants for different agents.
Would you like guidance on how to share a trunk across agents or an example of creating a SIP participant with a trunk?
Sources: Making calls overview | LiveKit docs | Python SDKs | livekit/livekit_composite | DeepWiki | sip | livekit/livekit_composite | DeepWiki | https://blog.livekit.io/introducing-livekit-phone-numbers-zero-to-ringing-in-60-seconds/#/portal/signup| | Outbound Calling Script | LiveKit docslate-glass-71191
12/28/2025, 10:03 PMtall-airline-16756
12/28/2025, 10:04 PMroomConfig.agents or specifying an agentName), so one trunk can route different incoming calls to different agents or rooms. LiveKit also offers LiveKit Phone Numbers, which are managed at the project level and use dispatch rules in the same way.
Would you like an example dispatch rule or steps to route calls to a specific agent?
Sources: sip | livekit/livekit_composite | DeepWiki | Agents telephony integration | LiveKit docs | https://blog.livekit.io/introducing-livekit-phone-numbers-zero-to-ringing-in-60-seconds/#/portal|late-glass-71191
12/29/2025, 9:38 AMtall-airline-16756
12/29/2025, 9:38 AMsip_number when creating a SIP participant — note this depends on your SIP provider allowing caller ID override.
Would you like an example of creating a SIP participant with a sip_trunk_id or help designing trunk/number mappings for your agents?
Sources: Agents telephony integration | LiveKit docs | sip | livekit/livekit_composite | DeepWiki | Outbound Calling Script | LiveKit docs | Preserving Caller ID During SIP Transfers in LiveKit Agentslate-glass-71191
12/29/2025, 9:42 AMtall-airline-16756
12/29/2025, 9:42 AMcall-...) or directly target an agent by agentName in roomConfig.agents.
• The dispatch rule creates/chooses a room and then the roomConfig routes the call to the specified agent(s).
Example flow (simple):
1. Call to number A arrives on inbound trunk X.
2. Dispatch rule for number A matches and targets room prefix call-A- or agent agentB.
3. LiveKit creates a room (e.g., call-A-123) and applies roomConfig that lists agentB.
4. Agent B is dispatched into that room to handle the call.
Options you can use:
• Direct agent mapping: set the dispatch rule to target agentName: "agentB".
• Room-based mapping: dispatch to a room prefix and use roomConfig to include specific agents or agent groups.
• Use multiple dispatch rules to route different numbers/conditions to different agents or queues.
If you want, I can:
• Provide an example dispatch rule JSON that routes number A to agent B.
• Suggest a pattern for room naming and agent pools if you need load-balancing or skill-based routing. Which would you prefer?late-glass-71191
12/29/2025, 9:43 AMtall-airline-16756
12/29/2025, 9:43 AMsip_trunk_id) and optionally which number to present as caller ID (sip_number).
• The agent who should make the call is the one that initiates the CreateSIPParticipant request (or your service creates the participant on behalf of that agent). That participant is tied to a room or agent identity so the call is associated with agent B.
• Practically you have two common patterns:
◦ Agent-initiated: Agent B triggers the call from their client; the client/server calls CreateSIPParticipant with sip_trunk_id (and optional sip_number) and the participant is created for Agent B’s room/identity — Agent B is the caller.
◦ Server-initiated on behalf of agent: Your server issues CreateSIPParticipant specifying the target room/agent identity (Agent B) so LiveKit creates the SIP leg and maps it into Agent B’s room; Agent B is then connected to the outbound call.
If you want, I can provide a sample CreateSIPParticipant request showing the fields to set (sip_trunk_id, sip_number, and the room/participant identity) for Agent B. Which pattern are you using: agent-initiated or server-initiated?late-glass-71191
12/29/2025, 9:46 AMtall-airline-16756
12/29/2025, 9:47 AMsip_number on CreateSIPParticipant), the call will present that number as the caller ID so recipients see the agent's number.
• LiveKit does not automatically infer agent identity just from caller ID. The agent association comes from who the participant/room/identity is for when you create the SIP participant. So you must:
◦ Create the SIP participant for Agent B's room/identity, and
◦ Specify the agent's phone number as the sip_number (or use the outbound trunk whose number matches the agent).
• Caller ID override depends on your SIP provider allowing sip_number passthrough. If the provider blocks overrides, use a dedicated outbound trunk whose number equals the agent's number.
If you'd like, I can:
• Provide a sample CreateSIPParticipant request that ties the outbound call to Agent B and sets sip_number, or
• Recommend trunk/number mapping patterns (one trunk per agent number vs shared trunks with sip_number override). Which do you prefer?late-glass-71191
12/29/2025, 9:53 AMInitiating call from ${fromNumber} to ${toNumber});
// // Validate agent
// const agent = await prisma.agents.findUnique({
// where: { id: agentId },
// });
// if (!agent) {
// throw new Error('Agent not found');
// }
// if (agent.status !== 'ACTIVE') {
// throw new Error('Agent is not active');
// }
// // Validate trunk
// const trunk = await prisma.sipOutboundTrunk.findUnique({
// where: { id: trunkId },
// });
// if (!trunk) {
// throw new Error('Trunk not found');
// }
// if (!trunk.numbers.includes(fromNumber)) {
// throw new Error(Number ${fromNumber} not configured on trunk);
// }
// Generate room name if not provided
const callRoomName = options?.roomName || `outbound-call-${Date.now()}`;
// const sipParticipantOptions = {
// participantIdentity: 'sip-test',
// participantName: 'Test Caller',
// krispEnabled: true,
// waitUntilAnswered: true
// };
// Create outbound call record in database
// const outboundCall = await prisma.outboundCall.create({
// data: {
// agent_id: agentId,
// trunk_id: trunkId,
// fromNumber,
// toNumber,
// roomName: callRoomName,
// status: 'INITIATED',
// },
// });
// console.log(:white_check_mark: Outbound call record created: ${outboundCall.id});
// Prepare participant metadata
// const participantMetadata = {
// // call_id: outboundCall.id,
// agent_id: agentId,
// from_number: fromNumber,
// to_number: toNumber,
// ...(options?.participantMetadata || {}),
// };
// Create SIP participant with full options
// const participantOptions = {
// // participant_identity: outbound-${outboundCall.id},
// participant_name: options?.displayName || Outbound Call to ${toNumber},
// participant_metadata: JSON.stringify(participantMetadata),
// display_name: options?.displayName, // Custom caller ID
// dtmf: options?.dtmf, // Extension codes (e.g., "*123#ww456")
// play_dialtone: options?.playDialtone ?? false, // Play dial tone while connecting
// wait_until_answered: options?.waitUntilAnswered ?? true, // Wait for answer
// krisp_enabled: options?.krispEnabled ?? true, // Noise cancellation
// };
// console.log('Creating SIP participant with options:', participantOptions);
const participantInfo = await this.sipClient.createSipParticipant(
trunkId,
toNumber,
callRoomName,
{
waitUntilAnswered: true,
krispEnabled: true
}
);
console.log(SIP participant created: ${participantInfo.participantId});
// Update call with participant ID and status
// const updatedCall = await prisma.outboundCall.update({
// where: { id: outboundCall.id },
// data: {
// participantId: participantInfo.participantId,
// status: participantInfo.participantId ? 'IN_PROGRESS' : 'RINGING',
// },
// });
return {
success: true,
// call: updatedCall,
participantInfo,
roomName: callRoomName,
message: options?.waitUntilAnswered
? 'Call answered and connected'
: 'Call initiated',
};
} catch (error: any) {
console.error('Failed to initiate call:', error);
if (error.metadata) {
const sipStatusCode = error.metadata['sip_status_code'];
const sipStatus = error.metadata['sip_status'];
console.error(SIP Error: ${sipStatusCode} - ${sipStatus});
throw new Error(
SIP Error: ${sipStatusCode} - ${sipStatus || error.message}
);
}
throw error;
}
}tall-airline-16756
12/29/2025, 9:53 AMfromNumber.
Short answer: not automatically. Setting fromNumber as the caller ID helps recipients see the agent's number, but LiveKit associates the call with an agent by the participant identity/room you create — not by caller ID alone. To guarantee Agent B handles the call, create the SIP participant for Agent B’s room/identity (or include Agent B’s identity in the participant metadata) when calling createSipParticipant.
What to change in your code (concise checklist):
• Ensure the participant identity is tied to the agent (e.g., participantIdentity: agent-${agentId}) or create the participant in Agent B’s room.
• Pass sip_number (or equivalent field your SDK uses) with fromNumber so caller ID displays correctly — only works if your SIP provider allows override.
• Keep sip_trunk_id as the org-level trunk.
• Include agent info in participant_metadata (e.g., agent_id, from_number) so you can reconcile records and routing on events/callbacks.
• If provider blocks caller ID override, use a trunk whose number matches the agent’s number.
If you want, I can:
• Provide a short sample createSipParticipant payload showing participantIdentity, participantMetadata, sip_number, and sip_trunk_id.
• Review a patched version of your function with those fields inserted. Which would you prefer?late-glass-71191
12/29/2025, 10:00 AMtall-airline-16756
12/29/2025, 10:00 AMparticipantIdentity: agent-${agentId} or place the participant into a room you treat as Agent B’s room (even if created dynamically).
• Include agent info in participant metadata
◦ Pass participant_metadata: JSON.stringify({ agent_id: agentId, from_number: fromNumber, ... }) so events/webhooks can reconcile calls to agents.
• Set caller ID explicitly
◦ Provide sip_number: fromNumber (or SDK equivalent) when creating the SIP participant so the recipient sees the agent’s number. This requires your SIP provider to allow caller ID override; otherwise use a trunk with that number.
• Keep using the org-level trunk
◦ Continue passing sip_trunk_id (your trunkId).
• Ensure your server or client is the one that initiates the CreateSipParticipant call on behalf of the agent
◦ Either the agent’s client calls the API, or your server calls it specifying the agent identity so LiveKit maps the SIP leg to that agent.
If you want, I can produce a minimal example payload for your createSipParticipant call (showing sip_trunk_id, sip_number, participant_identity, and participant_metadata) or patch your function to include these fields. Which would you prefer?late-glass-71191
12/29/2025, 10:42 AMtall-airline-16756
12/29/2025, 10:43 AMcreateSipParticipant function does. Below is a concise explanation tied to how the call is created and mapped into a LiveKit room.
Function purpose
• Initiates an outbound SIP call and bridges the phone leg into a LiveKit room as a SIP participant.
Top-level parameters
• sipTrunkId (string): ID of the outbound SIP trunk to use for the call. The trunk contains provider/auth details and determines how the call is sent.
• number (string): Destination phone number to dial (the call target, sent as sipCallTo).
• roomName (string): LiveKit room the SIP participant will join when the call connects.
• opts (CreateSipParticipantOptions, optional): Call-level options that control caller ID, identity, behavior, and features (see fields below).
• outboundTrunkConfig (SIPOutboundConfig, optional): Inline trunk configuration to use instead of referencing a pre-configured trunk (overrides sipTrunkId behavior).
Key fields in the constructed request and what they do
• sipTrunkId / trunk: selects the trunk used to place the outbound INVITE (organization-level trunk or inline config).
• sipCallTo: the number being dialed (destination).
• sipNumber (opts.fromNumber): caller ID presented to the callee. Only effective if the SIP provider allows caller ID override; otherwise use a trunk whose number matches the desired caller ID.
• roomName: name of the room the SIP participant will join (the phone leg is bridged into this room).
• participantIdentity: unique identity for the SIP participant in the room (use something encoding the agent, e.g., agent-{agentId} to tie the call to a specific agent).
• participantName / displayName: human-readable participant name shown in UIs.
• participantMetadata: arbitrary JSON metadata attached to the participant (use to store agent_id, from_number, call_id, CRM links, etc.).
• participantAttributes: key/value attributes published to the room (useful for client-side logic and event handling).
• dtmf: DTMF digits to send after connect (for IVR navigation, PIN entry).
• playDialtone / playRingtone: play local dial tone/ringtone in the room while dialing so agents hear dialing state.
• headers: custom SIP headers to include on the INVITE (for integrations needing SIP header data).
• includeHeaders: include inbound SIP headers from provider in participant attributes (useful for debugging or extracting SIP metadata).
• hidePhoneNumber: mask phone number in identities/logs for privacy.
• ringingTimeout: seconds to wait for ringing/answer before canceling or treating as failed.
• maxCallDuration: maximum call length in seconds (server-enforced cut-off).
• krispEnabled: enable Krisp noise cancellation on the phone audio.
• waitUntilAnswered: whether the API call should wait until the callee answers before returning; affects default timeout.
• timeout: overall RPC timeout for the request (defaults to 10s, or 60s when waitUntilAnswered is true).
Return
• Promise<SIPParticipantInfo>: contains participant ID, room, SIP call id, and current state (dialing/connected/ended) so you can track/monitor the call.
How this ties to agent mapping (practical notes)
• Caller ID alone does not bind the call to an agent. To ensure Agent B handles the outbound call:
◦ Create the SIP participant with a participantIdentity that encodes Agent B or place the participant into a room that Agent B will join.
◦ Include agent info in participantMetadata so webhooks/events can reconcile the participant to agent records.
◦ Set sipNumber if you want the callee to see Agent B’s phone number and your SIP provider allows overrides; otherwise use a trunk whose number equals the agent number.
If you want, I can:
• Provide a minimal JSON payload example for CreateSIPParticipantRequest showing the recommended fields to tie the call to an agent, or
• Patch your function to populate participantIdentity, participantMetadata, and sipNumber. Which would you like? Sources: Outbound Calling Script | LiveKit docs | Agents telephony integration | LiveKit docs | SIP Lifecycle Management Agent | LiveKit docs | Preserving Caller ID During SIP Transfers in LiveKit Agents | sip | livekit/livekit_composite | DeepWikilate-glass-71191
12/29/2025, 10:49 AMtall-airline-16756
12/29/2025, 10:49 AMparticipantName / displayName is the human-readable label shown in UIs and is typically set to represent the person you are calling (the callee).
Clarifications and best-practices:
• It can be any descriptive string — caller/callee name, “Customer”, CRM name, or phone number — whatever your UI/UX needs.
• Use participantMetadata to store authoritative data (e.g., customer_id, phone, CRM link). That lets you show a friendly displayName while reliably reconciling events to records.
• If you want the agent identity to be obvious in the room, encode the agent in participantIdentity (e.g., agent-{agentId}) and keep displayName for the remote person.
• For privacy, avoid putting sensitive PII in `displayName`; use hidePhoneNumber or mask in UI if needed.
Do you want a suggested displayName / participantMetadata schema for your app?late-glass-71191
12/29/2025, 10:58 AMtall-airline-16756
12/29/2025, 10:58 AMagentName / roomConfig.agents).
• Outbound: Outbound calls are initiated by creating a SIP participant and selecting an outbound trunk (and optionally sip_number). Routing for outbound is determined at call creation (which trunk, which room/participant identity) — dispatch rules do not affect outbound calls.
If you want, I can show a sample dispatch rule for inbound routing or a sample CreateSIPParticipant payload that ties an outbound call to a specific agent. Which would you prefer?late-glass-71191
12/29/2025, 11:01 AMtall-airline-16756
12/29/2025, 11:01 AM{
"sipTrunkId": "ST_abc123", // org-level trunk ID
"trunk": null, // optional inline trunk config (leave null if using sipTrunkId)
"sipCallTo": "+14155551234", // destination (callee)
"sipNumber": "+15551230000", // caller ID to present (agent's number)
"roomName": "agent-room-42", // room to bridge the phone leg into
"participantIdentity": "agent-42", // ties the SIP participant to Agent B
"participantName": "Acme Corp Caller",
"displayName": "Jane Customer",
"participantMetadata": "{\"agent_id\":\"42\",\"agent_name\":\"Agent B\",\"call_id\":\"out-9876\"}",
"participantAttributes": {
"agent_id": "42",
"from_number": "+15551230000",
"to_number": "+14155551234",
"call_type": "outbound"
},
"dtmf": null,
"playDialtone": true,
"krispEnabled": true,
"waitUntilAnswered": true,
"ringingTimeout": 60,
"maxCallDuration": 3600,
"headers": {
"X-Custom-Header": "some-value"
},
"hidePhoneNumber": false,
"includeHeaders": true
}
Key notes
• Use participantIdentity (e.g., agent-42) or the roomName that Agent B will join so LiveKit associates the SIP leg with that agent.
• sipTrunkId selects the org trunk; sipNumber sets caller ID (only works if your SIP provider allows override). If provider blocks overrides, use a trunk whose number equals the agent number.
• Put stable identifiers in participantMetadata (agent_id, call_id, CRM id) so webhooks/events can reconcile the participant to your agent records.
• participantAttributes are visible in the room for client logic and can be used to drive UI/state.
• waitUntilAnswered: true makes the API call wait until the callee answers before returning — useful so the agent only joins after connection.
• Adjust krispEnabled, timeouts, and DTMF as needed.
Would you like this as a TypeScript object or a patched version of your function that injects these fields?late-glass-71191
12/29/2025, 3:18 PMtall-airline-16756
12/29/2025, 3:19 PMsip_trunk_id when creating a SIP participant.
• Caller identity should be handled at call creation: set sipNumber (caller ID) and `participantIdentity`/`participantMetadata` to tie the call to an agent. If your SIP provider blocks caller ID overrides, use trunks mapped to the desired number(s), but still prefer a smaller set of trunks (per region/use-case), not per agent.
When separate trunks make sense:
• Regulatory/geographic requirements (different providers or number pools per region)
• Distinct business lines requiring separate providers or billing
• Test/staging isolation
Recommendation:
• Use shared outbound trunks (one or a few by region/use-case).
• Associate calls to agents via participantIdentity, participantMetadata, room assignment, and sipNumber when allowed.
• Create additional trunks only for regulatory, regional, or provider-specific needs — not per-agent.
Would you like a suggested mapping pattern (e.g., one trunk per region + caller ID override strategy) for your setup?
Sources: sip | livekit/livekit_composite | DeepWiki | Making calls overview | LiveKit docs | Outbound Calling Script | LiveKit docs | examples/voice_agents | livekit/livekit_composite | DeepWiki | Python SDKs | livekit/livekit_composite | DeepWikilate-glass-71191
12/29/2025, 5:37 PMListSIPOutboundTrunk API in node jstall-airline-16756
12/29/2025, 5:37 PMlate-glass-71191
12/29/2025, 5:55 PM