Skip to content

CreateSIPParticipant rejects valid Twilio X-Twilio-CallToken values over 1024 bytes #789

Description

@dtran26

Summary

A production warm transfer on LiveKit Cloud failed before the outbound SIP INVITE because CreateSIPParticipant rejected a Twilio-issued X-Twilio-CallToken whose value exceeded 1,024 bytes.

Twilio's Immutable Call Forwarding flow requires this token to be forwarded unchanged on the outbound leg so the original caller ID can be presented. The token is opaque and may contain SHAKEN/STIR and DIV PASSporTs, so truncating it is not valid.

Observed error

TwirpError: invalid header value for X-Twilio-CallToken:
header X-Twilio-CallToken: value too long (max 1024 characters)

HTTP status: 400
Twirp code: invalid_argument
at SipClient.createSipParticipant

The token was captured from the inbound Twilio SIP INVITE and passed back unchanged. Its value is intentionally omitted because it is a bearer credential.

Reproduction

  1. Receive an inbound call through Twilio Elastic SIP Trunking with Immutable Call Forwarding enabled.
  2. Capture the inbound X-Twilio-CallToken.
  3. Use the original caller number as sip_number and pass the unchanged token to CreateSIPParticipant:
{
  "sip_number": "<original caller number>",
  "headers": {
    "X-Twilio-CallToken": "<Twilio-issued token longer than 1024 bytes>"
  }
}
  1. LiveKit returns 400 invalid_argument before creating the outbound SIP leg.

Expected behavior

CreateSIPParticipant should support the sizes that Twilio can legitimately issue for X-Twilio-CallToken, or LiveKit should provide a documented alternative for this carrier integration.

Possible resolutions could include:

  • raising the per-header limit;
  • making it configurable; or
  • explicitly accommodating X-Twilio-CallToken.

Changing the outbound trunk transport to TCP does not avoid the current failure because request validation occurs before SIP traffic is sent.

Relevant implementation

The limit is currently hard-coded in the protocol validator:

https://github.com/livekit/protocol/blob/9aa28a1171907bad7098b59546a77ebdacd8e725/livekit/sip_validation.go#L243-L256

The corresponding tests describe the length restriction as implementation-specific and not part of RFC 3261:

https://github.com/livekit/protocol/blob/9aa28a1171907bad7098b59546a77ebdacd8e725/livekit/sip_validation_test.go#L79-L113

Twilio requires the CallToken to be retransmitted unchanged and documents that it can contain SHAKEN/STIR and DIV PASSporTs:

https://www.twilio.com/docs/voice/trusted-calling-with-shakenstir#call-forwarding

Environment

  • LiveKit Cloud
  • livekit-server-sdk: 2.15.3
  • @livekit/agents: 1.6.2
  • Observed: 2026-08-12

We can provide sanitized trace timing and Cloud job/room identifiers privately if useful.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions