Skip to content

Feature: Call transfer (blind and attended) #189

Description

@deepakjaiswal1

Summary

Add support for transferring an active call to another party.

Motivation

Call transfer is a core telephony feature missing from the current API. A softphone user needs to hand off an in-progress call to a third party.

Proposed capability

  • Blind (unattended) transfer: transfer the active call to a target number without first speaking to the target.
  • Attended (consultative) transfer: put the current call on hold, call the target, then connect the two.
  • Define the outcome for the transferring party's own session after a successful transfer (released vs. retained).

Open questions for the WG

  • New action/operation on webrtc-call-handling referencing the existing mediaSessionId, with a target identifier (phoneNumber)?
  • How does the transferor authorize the transfer target, and what happens if the target rejects/does not answer?
  • Notifications to affected parties via webrtc-events.
  • Relationship to the 3-way calling proposal (attended transfer and merge share a "consult then connect" flow) — worth aligning the two designs.

@stroncoso @teikuran

Activity

  1. deepakjaiswal1 commented on Sep 22, 2026

    @deepakjaiswal1
    CollaboratorAuthor

    @stroncoso @teikuran

    Did some design analysis on this, posting it ahead so we have something concrete to discuss rather than starting cold. Several of the positions below are ones I'd like input on rather than ones I'm settled on.

    IR.92 §2.3.11 requires the UE to support consultative ECT per TS 24.629, so effectively every VoLTE handset has this in its IMS stack. Yet no native dialer exposes it — iOS gives you Merge and Swap, the Google Phone app the same. Anyone who wants transfer today goes to a UC product (Webex, Teams Phone, One Talk), all of which implement it in their own stack, which means the call has left the operator network entirely.

    That's a better motivation than "softphones need transfer": the capability is already in the network and currently unreachable by developers. Those of you in Europe may have a different read on how widely ECT is provisioned — would like to hear it.

    Terminology and the two variants

    Using 3GPP terms: transferor (A) initiates, transferee (B) stays in the communication, transfer target (C) replaces A.

    • Blind — A sends REFER (RFC 3515) pointing at C, B establishes a session with C, A drops out. RFC 5359 §2.3.
    • Attended — A holds B, calls C, sends REFER with Replaces (RFC 3891). RFC 5359 §2.4.

    Neither needs a conferencing or mixing resource.

    One correction to an assumption I had wrong, since I doubt I'm alone: REFER-based transfer gives the transferee a new dialog. B's session with A goes away and a fresh one appears toward C. The model where B's dialog survives with a re-INVITE is B2BUA/3pcc behaviour — a legitimate ECT implementation, but a different one.

    Scoping: blind now, attended after #188

    Attended and 3-way calling share their entire setup phase and diverge only at the last step. Worse, my attended design requires the original session to be on Hold, and #188 is asking whether hold should stop being a status and become an action. We shouldn't answer that twice.

    So blind can proceed independently; attended should wait for #188. Sketch below anyway, so we can check the shapes are compatible.

    One caveat against my own sequencing: IR.92 mandates consultative on the UE, not blind. So the variant that's simpler to specify may be the one with weaker guaranteed field support. Worth a sanity check from anyone with visibility into live IMS provisioning.

    Endpoint

    POST /sessions/{mediaSessionId}/transfer
    

    A sub-resource rather than a new status on PUT /status, because transfer has a side effect outside the session — it originates a new leg — where everything currently on /status stays inside the existing two-party session.

    Blind:

    { "transferType": "blind", "transferTarget": "tel:+17085854001" }

    transferTarget reuses the existing Address schema, which takes both tel: and sip:. My original issue text said "phoneNumber" — that was sloppy, no reason to exclude SIP URI targets.

    Returns 202 Accepted; transferor session moves to Transferred and is released.

    Attended (provisional, pending #188):

    {
      "transferType": "attended",
      "transferTarget": "tel:+17085854001",
      "consultationSessionId": "0AEE1B58BAEEDA3EABA42B32EBB3DFE07E9CFF402EAF9EED"
    }

    Path mediaSessionId is the original call (held), consultationSessionId the active consultation, both under the same registrationId. transferTarget is arguably redundant here since it's derivable from the consultation session — I'd probably drop it, but it can wait.

    Failure model — main thing I'd like to discuss

    Blind here is genuinely cold. A sends REFER and releases immediately: no waiting for the final NOTIFY, no learning the outcome, no getting the call back.

    Stated plainly, because it shouldn't be left to implementers: if C is busy or doesn't answer, B's session terminates. B ends up with no call, having initiated nothing.

    I went back and forth on this. RFC 5359 has the transferor hold on until the NOTIFY precisely to avoid it, and it would cost us very little — the extra work is not sending the release yet. What settled it for me is that a user who wants a graceful handoff should be picking attended. That's what attended is for, and building recovery into blind transfer makes it into something it isn't.

    Worth noting as a counterpoint, though: Teams offers "Ring back if there's no answer" as an explicit option on transfer, sending the call back to the transferor if the target doesn't pick up. So at least one major UC product decided this mattered enough to build a toggle for. We could reasonably say that's a client-side product choice rather than an API concern, but I'd rather we said it deliberately.

    This reasoning does not carry to attended, where A holds two live sessions and dropping both would be user-hostile. The failure model may well differ per variant.

    What the transferee sees

    Follows from the above: B does not keep their mediaSessionId. Original session terminates, new inbound session appears. Client applications have to handle that, so it needs to be normative.

    Portability concern: an operator implementing ECT via 3pcc could keep B's dialog and re-INVITE it instead. Both are valid IMS implementations. If we don't mandate what the client observes, the same API behaves differently per operator — exactly what CAMARA exists to prevent.

    Status, errors, scope

    One new SessionStatus value: Transferred — terminal, distinguishes "ended because transferred" from hung up or failed. I originally had Transferring too, but with cold transfer there's no pending window to represent.

    One error: 409 INVALID_SESSION_STATE — session not transferable, or (attended) consultation not held / wrong registration. Deliberately no "target rejected" error: under the cold model that happens after the 202 and the transferor is already gone. INVALID_SESSION_STATE is invented; someone should check whether Commonalities already covers it.

    On authorization, I'd propose a dedicated webrtc-call-handling:sessions:transfer scope rather than reusing sessions:write. Today sessions:write means "modify a call I'm already in" — hold, resume, renegotiate. Transfer makes the network originate a leg to an address of the caller's choosing. A compromised app with sessions:write can currently disrupt calls; with transfer folded in, it can redirect them to premium-rate destinations. A separate scope also lets operators enable transfer for enterprise UC consumers and withhold it from consumer apps.

    Note the scope and endpoint decisions are coupled — if we model transfer as a status transition instead, reusing sessions:write becomes the consistent answer.

    Events

    For the transferor, blind is just 202 → session-status: Transferred → released. Which is rather the point.

    The transferee is the harder case: if B is also a client of this API they'd see their session terminate and a session-invitation for the new one, but usually B is just some phone somewhere. What we notify to parties outside the API consumer's scope is unresolved and probably needs its own discussion.

    Open questions

    Emergency sessions. An emergency call must not be transferable this way. Documented MUST NOT, network-enforced, or both? Should be reconciled with the emergency calling supporting doc.

    Identity through the transfer. I looked at how Teams handles this and it's instructive. Teams puts all its design attention on information flowing to the target — a call topic, Copilot-generated context — and a long-standing customer request is that the original caller's ID follow through a blind transfer, which today it doesn't; the target just sees the transferor. I found nothing suggesting the transferee is told who they're being connected to.

    So the state of the art in a mature UC product is: partial, and mostly in the other direction. For us the mechanism exists — connected identity (RFC 4916), surfaced in IMS as TIP — but it's subject to TIR and C may have withheld it, so it can't be unconditional. Two separate questions here really, and I don't think we should conflate them:

    1. Does the target learn who the original caller is, or only who transferred them?
    2. Does the transferee learn who they've been connected to?

    Next steps

    I'd like to walk through this on the next call — particularly the failure model, since most of the rest follows from it, and the scope question, which I expect is the most contentious part. No PR proposed at this stage; I'd rather the direction was settled first.

  2. stroncoso commented on Sep 22, 2026

    @stroncoso
    Contributor

    Hi @deepakjaiswal1 , many thanks for your contribution! Really great content here.

    From my point of view, I'm agree to focus on BLIND now, and cover ATTENDED later. It reduces scope, keeps things clear and initial specs for that end looks good. The new endpoint is clear, meaningful and easy to inject.

    Failure model

    Then, there is the transfer feature UX (user experience). Here is the main point that your comment describes. And of course, I'm happy to discuss what we want. As you said, BLIND is cold by design. If the call should hold or not is something that I was discussing for years... and I always said the same: "The RFC is like it is, your app should behave as your users prefer".

    Each app should take its own decision, what we should grant is the technical capability to do what they want. So my idea is that we can enhance the endpoint payload with some capabilities that we can offer: notify if failed, redial if failed, etc... (just examples). We can open a field there to allow vendors to handle the missed_blind_transfer event.

    We will discuss this and other topics next time. I will take also some time to investigate on my own.

    Again, thanks a lot for your contribution!

    Best regards

  3. deepakjaiswal1 commented on Oct 6, 2026

    @deepakjaiswal1
    CollaboratorAuthor

    @stroncoso thanks — happy with blind-first and the endpoint shape, and the capability-not-policy principle works for me.

    One thing worth walking through on the call: notify-if-failed means we can't release the transferor's session immediately. Once it's gone there's no dialog left to carry the final NOTIFY, so the network has to hold something until we know whether C answered. That brings back the Transferring status I'd dropped. Not an objection — just a consequence worth agreeing on before anyone writes schema.

    Two questions on your proposal:

    • Defined enum that all operators support, or an open field? The first keeps consumers portable, and apps still choose freely either way.
    • "Redial if failed" — does B retry C, or does the call come back to A? If it's the second, A's session has to stay up for the whole attempt, which makes it closer to a separate transfer mode than a flag. Receptionist recall is a real use case so I'm not pushing back, but it's a bigger piece than it looks.

    Still open from my earlier comment, and I think this is the one thing we have to decide rather than leave to implementations: does the transferee keep their mediaSessionId? REFER-based ECT says no — B's dialog tears down and a new one appears. A 3pcc/B2BUA implementation says yes — same dialog, re-INVITEd with new SDP. Both are valid, and the consumer has no way to tell which it's talking to, so there's no app preference to expose here. If we don't pin it down, the same application breaks when it moves to a second operator.

    My instinct is to mandate the REFER view, on the grounds that a 3pcc operator can always present terminate-plus-new-session at the API layer, where the reverse needs a B2BUA in the media path. But you'll know far better than I do whether the gateway can reasonably normalise this in either direction.

    Also still open: should transfer have its own OAuth scope (webrtc-call-handling:sessions:transfer) rather than reusing sessions:write? It's the one operation here that makes the network originate a leg to an address the consumer picks, which feels like a different kind of consent. @teikuran any view?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions