From b511709367b6f4840d61b342228f30e1392ddde4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 21 Aug 2020 13:06:00 +0100 Subject: [PATCH 01/12] Placeholder MSC for VoIP call transfers --- proposals/2747-voip-call-transfer.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 proposals/2747-voip-call-transfer.md diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md new file mode 100644 index 00000000000..818bd480f60 --- /dev/null +++ b/proposals/2747-voip-call-transfer.md @@ -0,0 +1,3 @@ +# MSC2747: Transferring VoIP Calls + +Placeholder MSC for transferring VoIP calls From 783d2884fc93891e5f278b574ff17d97c8df879d Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 26 Aug 2020 17:55:59 +0100 Subject: [PATCH 02/12] First draft of call transfer MSC --- proposals/2747-voip-call-transfer.md | 156 ++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 1 deletion(-) diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md index 818bd480f60..4bc9e1f627c 100644 --- a/proposals/2747-voip-call-transfer.md +++ b/proposals/2747-voip-call-transfer.md @@ -1,3 +1,157 @@ # MSC2747: Transferring VoIP Calls -Placeholder MSC for transferring VoIP calls +[MSC2746](https://github.com/matrix-org/matrix-doc/pull/2746) extends the Matrix +Voice over IP functionality with more reliability, hold/resume and DTMF. The ability +to transfer a call to another destination is absent from the current Matrix VoIP spec +and is not covered by MSC2746. + +Adding this will allow for scenarios such as: + * A customer service agent receiving a call using a Matrix client, then transferring + the customer to another department in the company. + * A personal assistant or switchboard operator calling another party on behalf of a + user, then connecting the user directly to their destination. + +This MSC builds on [MSC2746](https://github.com/matrix-org/matrix-doc/pull/2746), making +use of the `invitee` field on `m.call.invite` in particular. + +## Nomenclature + +Throughout this MSC, industry standard nomenclature is used to refer to parties involved +in the call transfer: + * Transferee: The party who is being transferred + * Transferor: The party initiating the transfer. + * Transfer target: The party that the transferee is being transferred to. + +## Proposal +This proposal introduces the `m.call.replaces` event which signals the intent of a +participant in a call to replace the call with another, such that the other participant +ends up in a call with a new user. This should appear as one, seamless call to the user +being transferred, with the possible exception of a permission prompt and some UI to +indicate that they are being transferred. + +An `m.call.replaces` event has fields: + * `call_id`: The ID of the call that the sender intends to replace + * `replacement_id`: An identifier for the call replacement itself, generated by the + transferor. + * `target_room`: Optional. If specified, the transferee client waits for an invite + to this room and joins it (possibly waiting for user confirmation) and then continues + the transfer in this room. If absent, the transferee contacts the Matrix User ID + given in the `target_user` field in a room of its choosing. + * `target_user`: An object giving information about the transfer target: + * `id`: The matrix user ID of the transfer target + * `display_name`: The display name of the transfer target. + * `avatar_url`: The avatar URL of the transfer target. + * `create_call`: If specified, gives the call ID for the transferee's client to use + when placing the replacement call. Mutually exclusive with `await_call`. + * `await_call`: If specified, gives the call ID that the transferee's client should wait + for. Mutually exclusive with `create_call`. + * `party_id`: The client's party ID for the call that it intends to replace. + +The display name and avatar URL of the transfer target in the `target_user` field +are purely informational and given by the transferor, so should be treated as such for +trust purposes. It is recommended that the transferor uses the transfer target's global +display name and avatar URL, or potentially those from the target room if available, +rather than details from a direct message with the transfer target: the display name and +avatar URL in the direct message room should be treated as private. + +From the transferor's point of view, a call transfer starts when they are in active calls +with both the transferee and the transfer target. One or both calls could be on hold and +the call with the transfer target may have not yet been answered (a 'blind transfer'). + +It also introduces an event to reject the transfer, `m.call.reject_replacement`, which has +fields: + * `call_id`: The ID of the call that was intended to be replaced + * `party_id`: The party ID of the client rejecting the replacement + * `replacement_id`: The replacement ID of the replacement that is being rejected + +To initiate a call transfer, the transferor: + * Attempts to find a suitable room. This should be a room that contains at least all three + users (and generally no others unless there is a specfic reason to user a certain room). + * If a suitable room cannot be found, it should create one, but it should not yet invite + the users, otherwise the transferee will receive the room invite before they receive the + call replace event. + * Once it has created a new room or found an existing one, it then sends two `m.call.replace` + events. One to the room for its call with the transfer target and one to the room for its + call with the transferee, each giving user information for the other ands with the + `call_id` field set to the call ID of the respective call. The `target_room` field + is newly created or chosen room in both cases. The transferor generates a new call ID and + puts this call ID in the `create_call` field in one replace event and in the `await_call` + field of the other. These can be either way around although it is suggested that the + transferee is instructed to create the new call. + * Once each event has been sent to each user, it can invite the corresponding user to the + target room (or may choose to wait for both replace events to send and invite both users + with a single API call). + * Additionally, once each replace event has been sent, it may choose to end the respective + call, although it would generally wait for the other parties to end them unless it is + explicitly intending to perform a blind transfer. + * The client may monitor the target room to observe the progress of the replacement call + being established. + +Upon receving an `m.call.replaces` event, a client behaves as follows: + * Checks that it is currently active in a call with call ID given in the `call_id` + field, that the other party in the call matches the sender of the replaces event and + that signalling for the call is being exchanged in the same room as the replaces event. + If any of these are not the case, the client ignores the event. + * Makes a decision on whether to act on the call transfer. How the client makes this decision + is not defined in this MSC. A client may, for example, wish to trust any user on specific + homeservers or in specific rooms or communities to transfer the user, or it may wish to + prompt the user, bearing in mind the display name and avatar of the transfer target supplied + by the transferor could be falsified. + * Once it has decided to act on the call transfer, it should continue to show the original call as + active (or represented in a 'transferring state') in the UI, even if the original call is hung up. + It continues to do so until the original call has either been replaced by the new call or the + replacement has failed. + * If the replace event has a `target_room` specified and the user is not already in the specified + room, it waits for an invite to that room to arrive, then accepts the invite. Once in the room, + if the `m.call.replaces` event had `create_call`, it sends an `m.call.invite` in the target room, + setting the `call_id` to the value of the `create_call` field and the `invitee` field to the + `id` field of `target_user`. If the replaces event contained `await_call`, the client waits + for a call with ID equal to that in the `await_call` field. + * If this call is sucessfully answred by the invitee, the client sends a hangup event in the + room for the origial call, ending the call. + +The `m.call.reject_replacement` is sent if the client does not accept the call transfer (eg. +it decides that the transferor is not sufficiently trustworthy, or it prompted the user and the +user chose to reject the transfer). The event has `replacement_id` equal to the `replacement_id` +of the `m.call.replaces` event that initiated the transfer. + +On receiving this, the transferor aborts the transfer process and informs the user that the call +transfer was rejected, and by which party. There is no explicit event to accept the transfer. + +## Potential issues +A call transfer is fairly complex and involves a lot of round-trips and state on clients, and +is fairly complex for clients to implement, in comparison to the rest of the VoIP spec which +is reasonably lightweight. If there were a PBX or soft switch on the path, this may potentially +handle the logic of doing the actual transfer meaning that the transferor would just need to +send a n `m.call.replaces` event to initiate the transfer, and clients would not have to +implement the rest of the protocol for being transferred if their leg of the call remained with +the PBX / soft switch. + +## Alternatives +No provision is made for a transferor to prompt a transferee to place a call to a +transfer target without there being an existing active call between the transferor +and the transferee. SIP does have this capability using the REFER method. This would +require a mechanism for the transferor to identify the transferee's individual devices, +akin to a GRUU in SIP, and be able to direct a specfic one of them to place the call. + +Equvialently, this could be achieved in a different way, for example, all the transferee's +devices could ring, and when they 'answer' on one of them, it places the call to the transfer +target. Similar behaviour can be achieved with the mechanisms described by this MSC, apart +from the fact that the initial incoming call to the transferee would be, and would appear as, +a normal incoming call from the transferor rather than being presented as a call to the +transfer target. + +Consideration was given to using a more generic event to refer conversations in general +between rooms as well as calls given the overlap in functionality. With threading support, +this could also transparently move threads between rooms. However, there are a number of +specific semantics associated with transferring calls specifically, and `m.call.replace` +better captures the behaviour of replacing the current call with a new one, so this MSC +opts to use a specific event for transferring calls. + +## Security considerations +The `target_user` field of the `m.call.replaces` event could be fabricated by the transferor, +as mentioned above. The transferee's client would have to present it to the user in this context. + +It would be up to clients to decide when to honour an incoming transfer request. If they accepted +any instruction to transfer the call, it would be possible to cause a user to place a VoIP call +to any Matrix user just by establishing a call to them and sending an `m.call.replaces` event. From 1d7101981214e128265debf280d553bf7ee71330 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 4 Sep 2020 10:29:57 +0100 Subject: [PATCH 03/12] Apply suggestions from code review Variety of typo fixes & clarifications Co-authored-by: Brendan Abolivier --- proposals/2747-voip-call-transfer.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md index 4bc9e1f627c..6caa5b734c5 100644 --- a/proposals/2747-voip-call-transfer.md +++ b/proposals/2747-voip-call-transfer.md @@ -30,7 +30,7 @@ being transferred, with the possible exception of a permission prompt and some U indicate that they are being transferred. An `m.call.replaces` event has fields: - * `call_id`: The ID of the call that the sender intends to replace + * `call_id`: The ID of the call that the transferor intends to replace * `replacement_id`: An identifier for the call replacement itself, generated by the transferor. * `target_room`: Optional. If specified, the transferee client waits for an invite @@ -64,9 +64,9 @@ fields: * `party_id`: The party ID of the client rejecting the replacement * `replacement_id`: The replacement ID of the replacement that is being rejected -To initiate a call transfer, the transferor: +To initiate a call transfer, the transferor's client: * Attempts to find a suitable room. This should be a room that contains at least all three - users (and generally no others unless there is a specfic reason to user a certain room). + users (and generally no others unless there is a specific reason to use a certain room). * If a suitable room cannot be found, it should create one, but it should not yet invite the users, otherwise the transferee will receive the room invite before they receive the call replace event. @@ -74,7 +74,7 @@ To initiate a call transfer, the transferor: events. One to the room for its call with the transfer target and one to the room for its call with the transferee, each giving user information for the other ands with the `call_id` field set to the call ID of the respective call. The `target_room` field - is newly created or chosen room in both cases. The transferor generates a new call ID and + is the newly created or chosen room in both cases. The transferor generates a new call ID and puts this call ID in the `create_call` field in one replace event and in the `await_call` field of the other. These can be either way around although it is suggested that the transferee is instructed to create the new call. @@ -108,7 +108,7 @@ Upon receving an `m.call.replaces` event, a client behaves as follows: `id` field of `target_user`. If the replaces event contained `await_call`, the client waits for a call with ID equal to that in the `await_call` field. * If this call is sucessfully answred by the invitee, the client sends a hangup event in the - room for the origial call, ending the call. + room for the original call, ending the call. The `m.call.reject_replacement` is sent if the client does not accept the call transfer (eg. it decides that the transferor is not sufficiently trustworthy, or it prompted the user and the @@ -134,7 +134,7 @@ and the transferee. SIP does have this capability using the REFER method. This w require a mechanism for the transferor to identify the transferee's individual devices, akin to a GRUU in SIP, and be able to direct a specfic one of them to place the call. -Equvialently, this could be achieved in a different way, for example, all the transferee's +Equivalently, this could be achieved in a different way, for example, all the transferee's devices could ring, and when they 'answer' on one of them, it places the call to the transfer target. Similar behaviour can be achieved with the mechanisms described by this MSC, apart from the fact that the initial incoming call to the transferee would be, and would appear as, From a392992a6539db28982a6c0cdc249b7cee6e863e Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 4 Sep 2020 10:53:34 +0100 Subject: [PATCH 04/12] Typo Co-authored-by: Brendan Abolivier --- proposals/2747-voip-call-transfer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md index 6caa5b734c5..92d4be260f2 100644 --- a/proposals/2747-voip-call-transfer.md +++ b/proposals/2747-voip-call-transfer.md @@ -72,7 +72,7 @@ To initiate a call transfer, the transferor's client: call replace event. * Once it has created a new room or found an existing one, it then sends two `m.call.replace` events. One to the room for its call with the transfer target and one to the room for its - call with the transferee, each giving user information for the other ands with the + call with the transferee, each giving user information for the other and with the `call_id` field set to the call ID of the respective call. The `target_room` field is the newly created or chosen room in both cases. The transferor generates a new call ID and puts this call ID in the `create_call` field in one replace event and in the `await_call` From 35f12d6d8d689bab2f5d23820295d494b8a643f8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 4 Sep 2020 10:53:43 +0100 Subject: [PATCH 05/12] Make display name / avatar_url optional & clarify transgeror's client --- proposals/2747-voip-call-transfer.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md index 92d4be260f2..57ffe2643f1 100644 --- a/proposals/2747-voip-call-transfer.md +++ b/proposals/2747-voip-call-transfer.md @@ -39,17 +39,18 @@ An `m.call.replaces` event has fields: given in the `target_user` field in a room of its choosing. * `target_user`: An object giving information about the transfer target: * `id`: The matrix user ID of the transfer target - * `display_name`: The display name of the transfer target. - * `avatar_url`: The avatar URL of the transfer target. + * `display_name`: (Optional) The display name of the transfer target. + * `avatar_url`: (Optional) The avatar URL of the transfer target. * `create_call`: If specified, gives the call ID for the transferee's client to use when placing the replacement call. Mutually exclusive with `await_call`. * `await_call`: If specified, gives the call ID that the transferee's client should wait for. Mutually exclusive with `create_call`. - * `party_id`: The client's party ID for the call that it intends to replace. + * `party_id`: The transferor's client's party ID for the call that it intends to replace. The display name and avatar URL of the transfer target in the `target_user` field are purely informational and given by the transferor, so should be treated as such for -trust purposes. It is recommended that the transferor uses the transfer target's global +trust purposes. They should be omitted if the target has no display name or avatar URL set, +respectively. It is recommended that the transferor uses the transfer target's global display name and avatar URL, or potentially those from the target room if available, rather than details from a direct message with the transfer target: the display name and avatar URL in the direct message room should be treated as private. From 05347c12c91fcb848d7fd644a1a20d62e01c9358 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 4 Sep 2020 10:56:10 +0100 Subject: [PATCH 06/12] clarify which user --- proposals/2747-voip-call-transfer.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md index 57ffe2643f1..528127dfe64 100644 --- a/proposals/2747-voip-call-transfer.md +++ b/proposals/2747-voip-call-transfer.md @@ -116,8 +116,9 @@ it decides that the transferor is not sufficiently trustworthy, or it prompted t user chose to reject the transfer). The event has `replacement_id` equal to the `replacement_id` of the `m.call.replaces` event that initiated the transfer. -On receiving this, the transferor aborts the transfer process and informs the user that the call -transfer was rejected, and by which party. There is no explicit event to accept the transfer. +On receiving this, the transferor aborts the transfer process and informs the transferor user +that the call transfer was rejected, and by which party. There is no explicit event to accept +the transfer. ## Potential issues A call transfer is fairly complex and involves a lot of round-trips and state on clients, and From 452f4b972eb4a7e681ad16e9e00da49a3db36d6c Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 4 Sep 2020 11:10:27 +0100 Subject: [PATCH 07/12] Flesh out `m.call.reject_replacement` to also signal when the replacement fails. --- proposals/2747-voip-call-transfer.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md index 528127dfe64..10e31190cef 100644 --- a/proposals/2747-voip-call-transfer.md +++ b/proposals/2747-voip-call-transfer.md @@ -64,6 +64,17 @@ fields: * `call_id`: The ID of the call that was intended to be replaced * `party_id`: The party ID of the client rejecting the replacement * `replacement_id`: The replacement ID of the replacement that is being rejected + * `reason`: The reason a replacement is being rejected. One of: + * `declined`: Either the user has declined the transfer, or the client has done so on + their behalf (eg. due to a policy set in their client). + * `failed_room_invite`: The transferee's client timed out whilst waiting for the room + invite to arrive + * `failed_call_invite`: The transferee's client timed out whilst waiting for the invite + for the replacement call to arrive. + * `failed_call`: The replacement call itself could not be made. The `call_failure_reason` + field may be used to give the reason the replacement call failed. + * `call_failure_reason`: (Optional) May be present if `reason` is `failed_call`, in which + case it gives the `reason` field from the replacement call's hangup event. To initiate a call transfer, the transferor's client: * Attempts to find a suitable room. This should be a room that contains at least all three @@ -107,7 +118,9 @@ Upon receving an `m.call.replaces` event, a client behaves as follows: if the `m.call.replaces` event had `create_call`, it sends an `m.call.invite` in the target room, setting the `call_id` to the value of the `create_call` field and the `invitee` field to the `id` field of `target_user`. If the replaces event contained `await_call`, the client waits - for a call with ID equal to that in the `await_call` field. + for a call with ID equal to that in the `await_call` field. It is up to the transferee's client + to decide how long to wait for each invite before timing out. If it times out, it sends an + `m.call.reject_replacement` event in the original room to signal that the replcaement has failed. * If this call is sucessfully answred by the invitee, the client sends a hangup event in the room for the original call, ending the call. From 876db93c4e1d280931f22b233f9776f1ca0a18d8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 14 Dec 2020 15:35:54 +0000 Subject: [PATCH 08/12] Add capabilities on call invite / offer for call transfers --- proposals/2747-voip-call-transfer.md | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md index 10e31190cef..563d22173fc 100644 --- a/proposals/2747-voip-call-transfer.md +++ b/proposals/2747-voip-call-transfer.md @@ -133,6 +133,36 @@ On receiving this, the transferor aborts the transfer process and informs the tr that the call transfer was rejected, and by which party. There is no explicit event to accept the transfer. +### Capability Advertisment +This proposal also introduces a field on `m.call.invite` and `m.call.answer` events at the top +level with the key `capabilities`, whose value is an object. This object has one recognised key, +`m.call.transferee` which, if set to true, states that the sender of the event supports the +`m.call.replaces` event and therefore supports being transferred to another destination. +For example: + +``` +{ + "type": "m.call.invite", + "room_id": "!rO0m_1d:example.org", + "content": { + "call_id": "123456", + "lifetime": 60000, + "capabilities": { + "m.call.transferee": true, + }, + "offer": { + "type": "offer", + "sdp": [...], + }, + }, +} +``` + +If this key is absent or set to anything othet than the boolean, `true`, or if +the `capabilities` object is missing altogether, it should be assumed that the +sender of the invite or answer does not support call transfers and clients should +reflect this in the UI accordingly. + ## Potential issues A call transfer is fairly complex and involves a lot of round-trips and state on clients, and is fairly complex for clients to implement, in comparison to the rest of the VoIP spec which From e68d9e70bd8e7d2ab14603f968d1e83e51255d4d Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 14 Dec 2020 18:30:46 +0000 Subject: [PATCH 09/12] Put call_id & party_id together --- proposals/2747-voip-call-transfer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md index 563d22173fc..45a9a933e80 100644 --- a/proposals/2747-voip-call-transfer.md +++ b/proposals/2747-voip-call-transfer.md @@ -31,6 +31,7 @@ indicate that they are being transferred. An `m.call.replaces` event has fields: * `call_id`: The ID of the call that the transferor intends to replace + * `party_id`: The transferor's client's party ID for the call that it intends to replace. * `replacement_id`: An identifier for the call replacement itself, generated by the transferor. * `target_room`: Optional. If specified, the transferee client waits for an invite @@ -45,7 +46,6 @@ An `m.call.replaces` event has fields: when placing the replacement call. Mutually exclusive with `await_call`. * `await_call`: If specified, gives the call ID that the transferee's client should wait for. Mutually exclusive with `create_call`. - * `party_id`: The transferor's client's party ID for the call that it intends to replace. The display name and avatar URL of the transfer target in the `target_user` field are purely informational and given by the transferor, so should be treated as such for From 13e4008b933b9353639fef469e37dd3315f90236 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 25 Jan 2021 16:24:59 +0000 Subject: [PATCH 10/12] Typo Co-authored-by: Brendan Abolivier --- proposals/2747-voip-call-transfer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md index 45a9a933e80..841c2304ef5 100644 --- a/proposals/2747-voip-call-transfer.md +++ b/proposals/2747-voip-call-transfer.md @@ -158,7 +158,7 @@ For example: } ``` -If this key is absent or set to anything othet than the boolean, `true`, or if +If this key is absent or set to anything other than the boolean, `true`, or if the `capabilities` object is missing altogether, it should be assumed that the sender of the invite or answer does not support call transfers and clients should reflect this in the UI accordingly. From fae016e81b1661a0e7d2b9c24027eabf651dbd97 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 25 Jan 2021 16:25:12 +0000 Subject: [PATCH 11/12] Add version to example Co-authored-by: Brendan Abolivier --- proposals/2747-voip-call-transfer.md | 1 + 1 file changed, 1 insertion(+) diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md index 841c2304ef5..668c5e9655b 100644 --- a/proposals/2747-voip-call-transfer.md +++ b/proposals/2747-voip-call-transfer.md @@ -154,6 +154,7 @@ For example: "type": "offer", "sdp": [...], }, + "version": 1, }, } ``` From b98c99f09f3d2fd41ae7af819d4473fcfad7d58d Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 6 Feb 2023 18:22:43 +0000 Subject: [PATCH 12/12] Move DTMF capability here (from 2746) --- proposals/2747-voip-call-transfer.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/proposals/2747-voip-call-transfer.md b/proposals/2747-voip-call-transfer.md index 668c5e9655b..e7431a5dce5 100644 --- a/proposals/2747-voip-call-transfer.md +++ b/proposals/2747-voip-call-transfer.md @@ -135,7 +135,7 @@ the transfer. ### Capability Advertisment This proposal also introduces a field on `m.call.invite` and `m.call.answer` events at the top -level with the key `capabilities`, whose value is an object. This object has one recognised key, +level with the key `capabilities`, whose value is an object. We define the key, `m.call.transferee` which, if set to true, states that the sender of the event supports the `m.call.replaces` event and therefore supports being transferred to another destination. For example: @@ -164,6 +164,9 @@ the `capabilities` object is missing altogether, it should be assumed that the sender of the invite or answer does not support call transfers and clients should reflect this in the UI accordingly. +We also define a capability called `m.call.dtmf`. Clients should only display UI for sending +DTMF during a call if the other party advertises this capability (boolean value `true`). + ## Potential issues A call transfer is fairly complex and involves a lot of round-trips and state on clients, and is fairly complex for clients to implement, in comparison to the rest of the VoIP spec which