Packages:
@telnyx/react-voice-commons-sdk 0.4.3
@telnyx/react-native-voice-sdk 0.4.5
Summary
TelnyxVoipClient.newCall() types customHeaders as Record<string, string>, but passes
it through unconverted to @telnyx/react-native-voice-sdk, whose CallOptions.customHeaders
expects { name: string; value: string }[]. The object is serialized verbatim into the
custom_headers field of the SIP INVITE message, which Telnyx ignores because it isn't the
expected array shape.
Result: any X-headers passed to newCall() never reach Telnyx, so they are absent from the
call.initiated Call Control webhook. There is no error or warning — the headers just vanish.
Impact
Apps that use custom SIP headers to correlate outbound WebRTC calls with backend state
(e.g. an X-CallId that the Call Control webhook handler uses to attach client_state)
silently lose that correlation. In our case every mobile-originated outbound call left a
permanently stale call record and was never recorded, because call.initiated /
call.answered / call.hangup all arrived without the header / client_state.
The same call placed via @telnyx/webrtc in the browser (which natively accepts the
{ name, value }[] shape) works correctly.
Where it happens
telnyx-voip-client.ts — public API accepts a record:
newCall(
destination: string,
callerName?: string,
callerNumber?: string,
customHeaders?: Record<string, string>
): Promise<Call>;
internal/calls/call-state-controller.ts (newCall) — passed through as-is:
const callOptions = {
destinationNumber: destination,
callerIdName: callerName,
callerIdNumber: callerNumber,
customHeaders, // <-- still Record<string, string>
};
const telnyxCall = await this._sessionManager.telnyxClient.newCall(callOptions);
@telnyx/react-native-voice-sdk call-options.ts — expects an array:
customHeaders?: { name: string; value: string }[];
@telnyx/react-native-voice-sdk messages/call.ts (createInviteMessage) — serialized verbatim:
custom_headers: callOptions.customHeaders,
Notably, the conversion IS done correctly on the CallKit answer path
(client.ts, executePendingAnswer):
// Convert Record<string, string> to { name: string; value: string }[] format
const customHeaders = Object.entries(this.pendingCustomHeaders).map(([name, value]) => ({
name,
value,
}));
…but the equivalent conversion is missing on the newCall path.
Repro
- Place an outbound call from a credential connection via the commons SDK:
await voipClient.newCall("+15551234567", undefined, "+15557654321", {
"X-CallId": "abc123",
});
- Inspect the
call.initiated Call Control webhook for the resulting call.
Expected: payload.custom_headers contains { "name": "X-CallId", "value": "abc123" }.
Actual: payload.custom_headers is absent/empty.
Suggested fix
In CallStateController.newCall() (or TelnyxVoipClient.newCall()), mirror the
executePendingAnswer conversion:
customHeaders: customHeaders
? Object.entries(customHeaders).map(([name, value]) => ({ name, value }))
: undefined,
Workaround
Pass the array shape and cast past the public type:
const headers = [
{ name: "X-CallId", value: callId },
] as unknown as Record<string, string>;
await voipClient.newCall(destination, undefined, callerNumber, headers);
Packages:
@telnyx/react-voice-commons-sdk0.4.3@telnyx/react-native-voice-sdk0.4.5Summary
TelnyxVoipClient.newCall()typescustomHeadersasRecord<string, string>, but passesit through unconverted to
@telnyx/react-native-voice-sdk, whoseCallOptions.customHeadersexpects
{ name: string; value: string }[]. The object is serialized verbatim into thecustom_headersfield of the SIP INVITE message, which Telnyx ignores because it isn't theexpected array shape.
Result: any X-headers passed to
newCall()never reach Telnyx, so they are absent from thecall.initiatedCall Control webhook. There is no error or warning — the headers just vanish.Impact
Apps that use custom SIP headers to correlate outbound WebRTC calls with backend state
(e.g. an
X-CallIdthat the Call Control webhook handler uses to attachclient_state)silently lose that correlation. In our case every mobile-originated outbound call left a
permanently stale call record and was never recorded, because
call.initiated/call.answered/call.hangupall arrived without the header /client_state.The same call placed via
@telnyx/webrtcin the browser (which natively accepts the{ name, value }[]shape) works correctly.Where it happens
telnyx-voip-client.ts— public API accepts a record:internal/calls/call-state-controller.ts(newCall) — passed through as-is:@telnyx/react-native-voice-sdkcall-options.ts— expects an array:@telnyx/react-native-voice-sdkmessages/call.ts(createInviteMessage) — serialized verbatim:Notably, the conversion IS done correctly on the CallKit answer path
(
client.ts,executePendingAnswer):…but the equivalent conversion is missing on the
newCallpath.Repro
call.initiatedCall Control webhook for the resulting call.Expected:
payload.custom_headerscontains{ "name": "X-CallId", "value": "abc123" }.Actual:
payload.custom_headersis absent/empty.Suggested fix
In
CallStateController.newCall()(orTelnyxVoipClient.newCall()), mirror theexecutePendingAnswerconversion:Workaround
Pass the array shape and cast past the public type: