transferSIPParticipant: No event or callback when transferred call is declined by the receiving

I’m using transferSIPParticipant to transfer SIP calls. I’m seeing different behavior depending on how the transferred call ends:

  • If the receiving operator does not answer, the configured timeout works as expected and an error is returned.

  • If the receiving operator accepts the transfer, the call proceeds normally.

  • However, if the receiving operator actively declines or rejects the transferred call, I don’t receive any event, callback, or error indicating that the transfer was declined. Instead, it behaves the same as a successful transfer, so my application cannot distinguish between an accepted and a declined

    Is there any event, hook, or API that reports when the receiving party explicitly declines the transferred call? If not, what is the recommended way to detect this scenario and handle it correctly?

There’s no dedicated “transfer declined” event in LiveKit. With a cold transfer, TransferSIPParticipant is a synchronous call — it returns success or it throws. There’s no separate async callback/webhook that fires later for a decline. So the only signal you get is the return/exception of that one API call, and whether a decline surfaces there depends entirely on what your SIP provider relays back.

is there any workaround i can apply for this ?

Cc: @sahil.tulani

CWilson’s “depends on what your provider relays” has a concrete mechanism. transferSIPParticipant sends a REFER and waits for the provider’s REFER NOTIFY; LiveKit maps that NOTIFY’s sipfrag as 1xx = keep waiting, exactly 200 = success, anything else (including 603 Declined / 486 Busy) = transfer-failed error (protocol.go handleReferNotify). So a decline does throw, but only if your provider sends a final failure NOTIFY. Silent success means it is sending a 200-class NOTIFY (or no final NOTIFY) on decline. Check your trunk’s SIP logs for the REFER NOTIFY on a declined transfer: if it shows 200, that is the cause; if it shows 603/486 and still does not surface, that is a bug worth filing.

Provider-independent workaround: do an attended transfer, not a blind REFER. Dial the operator yourself with createSIPParticipant into a consultation room, where you read that leg’s INVITE final status directly (200 answered vs 486/603 declined) plus join/leave events (outbound.go), then move the caller only once they answered.