Skip to content
Open
Changes from 51 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
c9355e1
add initial msc for call ringing
toger5 Nov 8, 2023
ed894b3
update msc number
toger5 Nov 8, 2023
ce551cb
json->plaintext for example
toger5 Nov 8, 2023
49190d1
markdown lint
toger5 Nov 8, 2023
1cecf69
review changes
toger5 Nov 9, 2023
a00113e
review fixes
toger5 Feb 19, 2024
a57720b
add Limitations and recommendations
toger5 May 13, 2024
b7febd1
fixes
toger5 Jul 10, 2024
109af47
Add extensible event fallback
toger5 Oct 8, 2024
655bcb3
make ugly json highlighting errors go away
ara4n Nov 25, 2024
545931d
readability fixes
ara4n Nov 25, 2024
04635eb
Latest
hughns Dec 17, 2024
0fb5fab
rename `m.rtc.notify` -> `m.rtc.noitfication`
toger5 Jul 15, 2025
7c996bb
introduce relation (instead of copying the session data into the noti…
toger5 Jul 15, 2025
6362c23
add `lifetime`
toger5 Jul 15, 2025
aee19b0
reference to rind duration discussion
toger5 Jul 15, 2025
93f3177
add extensible events section
toger5 Jul 15, 2025
ecd045a
update relation strings
toger5 Jul 18, 2025
68119da
Iterating:
toger5 Aug 26, 2025
ffaad72
Update proposal to latest flavour of MatrixRTC and include outcome of…
Johennes Jun 1, 2026
ed9e4c3
Make required properties more explicit
Johennes Jun 1, 2026
39cc547
Fix typo
Johennes Jun 1, 2026
bce0dc9
Update proposal with latest draft thinkng
Johennes Sep 4, 2026
cf39df3
Fix links
Johennes Sep 4, 2026
b7c9c5c
Add unstable identifier for push rule ID
Johennes Sep 4, 2026
5ebccfd
Adapt title
Johennes Sep 4, 2026
fbf6406
client -> user
Johennes Sep 4, 2026
d6019e0
Don't overstate invalidation
Johennes Sep 4, 2026
31e0305
Cap the lifetime and mention power levels
Johennes Sep 4, 2026
dd981dc
Handle duplicate invites
Johennes Sep 4, 2026
0c409ff
Detail sticky events usage
Johennes Sep 4, 2026
70a8744
Add row about withdrawing invites
Johennes Sep 4, 2026
967897d
Cover encrypted rooms
Johennes Sep 4, 2026
3041c95
Further stricky tweaks
Johennes Sep 4, 2026
51f9a03
Further stricky tweaks
Johennes Sep 4, 2026
30e770d
Fix sticky duration
Johennes Sep 4, 2026
8e4f625
Relax duplicates requirement
Johennes Sep 4, 2026
be8a4f8
Add repeated invites
Johennes Sep 4, 2026
540aac1
Add lifetime cap
Johennes Sep 4, 2026
285da5e
Fix typos
Johennes Sep 4, 2026
d6b2086
Further sticky fixes
Johennes Sep 4, 2026
b5e1c5d
Reorganise sections
Johennes Sep 4, 2026
b3ab4a8
Mention lifetime
Johennes Sep 4, 2026
15c4a9e
Fix typos
Johennes Sep 4, 2026
bbfdaaa
Push rule fixes
Johennes Sep 4, 2026
9b02dd1
Small clarification
Johennes Sep 4, 2026
e93e6f3
Small clarification
Johennes Sep 4, 2026
1fc7f1a
Small clarification
Johennes Sep 4, 2026
b28dd34
Small clarification
Johennes Sep 4, 2026
f9c5215
Small clarification
Johennes Sep 4, 2026
5db53dd
Fix typo
Johennes Sep 4, 2026
f174955
Clarify decline event's sticky key
Johennes Sep 8, 2026
6af6849
Clarify location of the slot event
Johennes Sep 8, 2026
1270349
Use call intent as example for future app-specific extensions
Johennes Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
382 changes: 382 additions & 0 deletions proposals/4075-rtc-notification-event.md
Comment thread
Johennes marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,382 @@
# MSC4075: MatrixRTC invites and notifications

[MSC4143] introduces MatrixRTC as an extensible framework for real-time communication in Matrix.
MatrixRTC sessions are formed around `m.rtc.slot` state events to which users join by sending
`m.rtc.member` room events.

This proposal introduces a scheme for notifying users about MatrixRTC sessions. The goal explicitly
is to construct a minimal solution that achieves feature parity with notifications in [legacy VoIP]
and doesn't design out future improvements. This is achieved by mediating notifications via dedicated
room events that prompt recipients to join a MatrixRTC session. Invites can be accepted by joining
the session with an appropriate `m.rtc.member` event, declined by sending a dedicated decline event
or ignored entirely. Under this model, notifications are agnostic of the particular MatrixRTC
application, yet form a solid foundation for future extension.

[MSC4143]: https://github.com/matrix-org/matrix-spec-proposals/pull/4143
[legacy VoIP]: https://spec.matrix.org/v1.19/client-server-api/#voice-over-ip

## Proposal

Two new room events `m.rtc.invite` and `m.rtc.decline` are introduced. These events can be sent by
clients to prompt other users to join a MatrixRTC session and to reject a received invitation, respectively.

The schema of `m.rtc.invite` is as follows:

- `slot_id` (required, string): The `state_key` of the `m.rtc.slot` event for which the invite is
handed out.
- `sender_ts` (required, integer): The timestamp (in milliseconds since the epoch) when the sending
client created the event.
- `lifetime` (required, integer): The time in milliseconds that the invite is valid for. MUST be
non-negative and SHOULD NOT be larger than 2 minutes. The RECOMMENDED value is 90 seconds.
- `m.mentions`: (optional, object): A [mentions] object to optionally direct the invite at a set
of users in the room. If omitted or empty, the event is not targeted at specific users and may
be acted upon by any room member.
Comment on lines +31 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmartinesp raised the question of how clients are supposed to decide if and which mentions to include.

Naively, I think in DMs, you would probably always include the other user in the mention because you want to ping/ring them. In rooms, you'd use either a room-mention (if you want to ping/ring the room) or no mention at all (if you want to achieve a non-ringing / banner notification at most).

Allowing no mentions at all also helps still allow calls and invites when users lack the power level for room mentions.

All of this should probably be added to the proposal text.

- `sticky_key` (required, string): The event's sticky key as per [MSC4354]. MUST be equal to `slot_id`.
This ensures that receivers only maintain one active invite per slot and sender.

```json5
{
"type":"m.rtc.invite",
"event_id": "$1",
"content": {
"slot_id": "m.call#room", // = m.rtc.slot state_key
"sender_ts": 1784493900000, // July 19, 2026 at 8:45pm UTC
"lifetime": 90000, // 90s
"m.mentions": { "user_ids": ["@alice:example.org"] }, // Optional
"sticky_key": "m.call#room", // = slot_id
},
...
}
```

The schema of `m.rtc.decline` is as follows:

- `m.relates_to` (required, object): An `m.reference` relation to the `m.rtc.invite` event which is
being declined.
- `sticky_key` (required, string): The events sticky key as per [MSC4354]. MUST be equal to the
event ID of the event that is being declined.
Comment thread
Johennes marked this conversation as resolved.
Outdated

```json5
{
"type": "m.rtc.decline",
"content": {
"m.relates_to": {
"rel_type": "m.reference",
"event_id": "$1"
},
"sticky_key": "$1"
}
}
```

Clients MUST send both `m.rtc.invite` and `m.rtc.decline` as sticky events as per [MSC4354] for the
associated delivery guarantee. The sticky duration for `m.rtc.invite` events SHOULD NOT be smaller
than the invite's `lifetime`. The sticky duration for `m.rtc.decline`, in turn, SHOULD NOT be smaller
than the declined invite's sticky duration. Additionally, clients MUST implement the ephemeral map
algorithm as per [MSC4354] to construct a state-like store of both invite and decline events.

[mentions]: https://spec.matrix.org/v1.19/client-server-api/#user-and-room-mentions
[MSC4354]: https://github.com/matrix-org/matrix-spec-proposals/pull/4354

### Sending invites

Both `m.rtc.invite` and `m.rtc.decline` MUST be sent encrypted when the room is encrypted.

A sending client SHOULD only consider an extended invite valid as long as all of the following
conditions apply:

- An `m.rtc.slot` event with `state_key = slot_id` and `status = "open"` exists in the room
Comment thread
Johennes marked this conversation as resolved.
Outdated
where the invite was sent.
- The invite's `lifetime`, as measured from `sender_ts` and capped at 2 minutes, has not elapsed.
- There are targeted room members who have neither accepted the invite (by sending a corresponding
`m.rtc.member` event) nor declined it (by sending an `m.rtc.decline` event).

To prevent duplicate invitations, senders SHOULD NOT emit invites when another valid invite exists
for the same slot and the same set of targeted users.

An existing invite MAY be withdrawn by sending another `m.rtc.invite` event with the same `sticky_key`
and an otherwise empty content and the same or a larger sticky duration. Alternatively, the event
MAY also be redacted which will remove it from the ephemeral map of sticky events.

How exactly sending clients present extended invitations in their UI is left as an implementation
detail. For instance, a sending client could use a ringing UI in [direct chats] while it is waiting
for the invite to be acted on and stop ringing when the invite is accepted or declined (see the next
section).

### Receiving invites

In line with the expected behaviour of sending clients that was outlined in the previous section,
a receiving client SHOULD only consider an invite valid as long as all of the following conditions
apply:

- The invite is the current invite entry in the ephemeral sticky events map for the sender
and slot and not a withdrawal (that is, an invite event whose `content` is empty except
for `sticky_key`).
- The client's current [push rules] produce an action of `notify` for the event.
- An `m.rtc.slot` event with `state_key = slot_id` and `status = "open"` exists in the room
where the invite was received.
- The `lifetime`, as measured from `sender_ts` and capped to 2 minutes, has not elapsed. If
`sender_ts` is more than 20 seconds ahead of `origin_server_ts`, the `lifetime` SHOULD be
measured from `origin_server_ts` instead. This limits the impact of a malicious user faking
`sender_ts` to trigger long-lived notifications.
Comment on lines +118 to +121

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But then the remote server could choose to put origin_server_ts in the future as well, so I think this ultimately needs to be limited by the receipt time on the client. (Ideally it would be limited by the receipt time on the local server, so there's no risk of a notification that "comes back from the dead" every time you log in a new device… but an MSC fixing age / introducing a new received_ts thing is of course a whole nother project)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, good point. The 2 minute cap would still apply there, too, though. So I think both malicious users and malicious servers could only cause 2-minute rings – which I think is fine?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 2 minute cap would still apply there, too, though.

Ah, I guess I can read "The lifetime, as measured from sender_ts and capped to 2 minutes, has not elapsed" that way, yes. My initial reading led me to think this was instructing to use stop_ringing_ts = origin_server_ts + min(lifetime, 2 minutes) rather than ring_duration = min(origin_server_ts + lifetime - now, 2 minutes).

- `m.mentions` is either empty, missing or contains the client's user ID (either directly or
through a room mention).
Comment on lines +122 to +123

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this redundant to the "push rules produce an action of notify" condition?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, something is wrong here. 🤔

I think we should separate validating invites from invite notifications. The latter only determine whether you get a ping / ring. But you should be able to also act on invites in a muted room when you observe them. So the validity rules here should only determine whether an invite is actionable for you.

For that, I think, we'd need the m.mentions condition because an invited directed at another user should not be actionable to you. But we should drop the notify condition from above because you that only determines whether you were notified.

Does that make sense?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense!

- The user is not already joined to the same slot via a corresponding `m.rtc.member` event.

If the invite is valid, the receiving client has three options:

1. It can accept the invite by joining the slot with an appropriate `m.rtc.member` event as
per [MSC4143]. Once the event is observed by other devices of the user, it invalidates the
invite.
1. It can decline the invite by sending an `m.rtc.decline` event. Again, once the event is
observed by other devices of the user, it invalidates the invite.
1. It can ignore the event by doing nothing. The invite will remain valid until either
the user accepts or declines the invite on another device or its `lifetime` has elapsed.
Comment on lines +108 to +134

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With legacy VoIP, Element clients had a variety of issues with stale invites coming back from the dead on client startup or in cases of poor push/sync performance. I see a potential for client devs to fall into very similar traps with m.rtc.invite if they implement the above bullet list naively, but I think we could significantly improve the odds of a decent implementation if we essentially fold the "three options" specification at the end (join/decline/timeout) into the bullet list, and then make the "three options" explanation non-normative.

Specifically the problem I see is that clients can have gappy syncs, syncs that contain not only an invite but also a join event followed by a leave event, etc., and it's not clear what it means to "observe" a join or decline event in all these cases. Therefore it might be better to phrase the conditions in terms of "what's currently sticky" and "what's in the ephemeral map", which are more well-defined.

I had written out more of my thinking here, but it's probably easiest if I send a PR to show what I mean?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had written out more of my thinking here, but it's probably easiest if I send a PR to show what I mean?

Yes, that would be awesome. I think I haven't yet fully understood the change you propose but it'll probably get clearer then.


If multiple valid invites for the same slot exist, clients SHOULD only consider the one whose
`lifetime` will expire last.

Again, how exactly receiving clients render invites in their UI is left as an implementation
detail. A reasonable choice could, for instance, be to use a ringing UI in [direct chats] and
a banner notification in group chats.

Clients may also tweak their notification UI based on the referenced `m.rtc.slot` event and the
`m.rtc.member` events currently joined to that slot. As an example, a client could choose to only
ring for invites to slots hosting an `m.call` application. Similarly, the client could evaluate
the `intent` property from [MSC4196] to display invites to audio calls differently than invites to
video calls.
Comment thread
Johennes marked this conversation as resolved.

[push rules]: https://spec.matrix.org/v1.19/client-server-api/#push-rules
[direct chats]: https://spec.matrix.org/v1.19/client-server-api/#direct-messaging
[MSC4196]: https://github.com/matrix-org/matrix-spec-proposals/pull/4196

### Push rules

In order to allow clients to manage their notification settings for MatrixRTC invites, three new default
push rules are introduced.

`.m.rule.rtc.invite_for_me` matches `m.rtc.invite` events which contain the user's Matrix ID in
the list of `user_ids` under `m.mentions`.

```json5
{
"rule_id": ".m.rule.rtc.invite_for_me",
"default": true,
"enabled": true,
"conditions": [{
"kind": "event_match",
"key": "type",
"pattern": "m.rtc.invite"
}, {
"kind": "event_property_contains",
"key": "content.m\\.mentions.user_ids",
"value": "[the user's Matrix ID]"
}],
"actions": ["notify", {
"set_tweak": "sound",
"value": "ring"
}]
}
```

`.m.rule.rtc.invite_for_room` matches `m.rtc.invite` events with the `room` property of `m.mentions`
set to `true` (provided that the sender has the proper power level to trigger room notifications).
Comment on lines +182 to +183

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A downside of reusing m.mentions for invites is that the distinction between a room: true invite and an invite without m.mentions is pretty subtle, I don't know how you'd explain that in a user interface 😕

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, yeah. I think we'd have the same problem if we introduced a separate invitees property (similar to legacy VoIP's invitee)?

I would naively say that without mentions, a call shouldn't ring whereas with mentions it should. Maybe we could achieve that by removing the ring tweak from the underride rule? 🤔

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we switched to an invitees property, then we could rule out the existence of something like an m.mentions: { room: true } invite though, no? (Or are you imagining invitees would exist in addition to m.mentions?)

In my mind it would be ideal to have just two variants: invites with specific invitees, and invites directed at the general room. We shouldn't need a third variant that's more 'passive' than a room invite, because you can always just start a session without sending any invites. I'm not experienced enough with push rules to know whether that's doable…


```json5
{
"rule_id": ".m.rule.rtc.invite_for_room",
"default": true,
"enabled": true,
"conditions": [{
"kind": "event_match",
"key": "type",
"pattern": "m.rtc.invite"
}, {
"kind": "event_property_is",
"key": "content.m\\.mentions.room",
"value": true
}, {
"kind": "sender_notification_permission",
"key": "room"
}],
"actions": ["notify", {
"set_tweak": "sound",
"value": "ring"
}]
}
```

Finally, `.m.rule.rtc.invite` matches any `m.rtc.invite` event.

```json5
{
"rule_id": ".m.rule.rtc.invite",
"default": true,
"enabled": true,
"conditions": [{
"key": "type",
"kind": "event_match",
"pattern": "m.rtc.invite"
}],
"actions": ["notify", {
"set_tweak": "sound",
"value": "ring"
}]
}
```

The rules are inserted into the existing default rule set as follows:

- `.m.rule.rtc.invite_for_me` is inserted as an [`override`] rule before `.m.rule.is_user_mention`.
- `.m.rule.rtc.invite_for_room` is inserted as an [`override`] rule before `.m.rule.is_room_mention`.
- `.m.rule.rtc.invite` is inserted as an [`underride`] rule before `.m.rule.call`.

The rules and their placement are designed to fit in with the common push rule configurations for setting
rooms to muted or mentions-only.

Muting is usually implemented via a user-defined `override` rule with empty `actions`. This overrides all
three rules and silences any notification for `m.rtc.invite` events.

Mentions-only rooms are commonly implemented via a `room`-kind rule with empty `actions`. The override
rules `.m.rule.rtc.invite_for_me` and `.m.rule.rtc.invite_for_room` are processed before such a rule.
As a result, `m.rtc.invite` events that include the user in `m.mentions` still cause notifications.
The `.m.rule.rtc.invite` underride rule, however, is processed after the `room`-kind rule. Consequently,
`m.rtc.invite` events with empty or no `m.mentions` don't cause notifications. This behaviour is
analogous to normal messages with `m.mentions`.

Furthermore, the placement of `.m.rule.rtc.invite_for_me` and `.m.rule.rtc.invite_for_room` before
`.m.rule.is_user_mention` and `.m.rule.is_room_mention` means that invites which target the user via
`m.mentions` can be muted by setting empty `actions` on these rules.

Finally, in rooms that are neither set to muted nor mentions-only, `m.rtc.invite` events with `m.mentions`
notify if the user is validly targeted via the event's `m.mentions` (via one of the two override rules)
and also if the event has no or empty `m.mentions` (via the underride rule).

| Push rule configuration | Invite with room mention | Invite with user mention | Invite without mention |
| ----------------------- | ------------------------ | ------------------------ | ---------------------- |
| Default | ✅ Notifies | ✅ Notifies | ✅ Notifies |
| Mentions-only | ✅ Notifies | ✅ Notifies | ❌ Silent |
| Muted | ❌ Silent | ❌ Silent | ❌ Silent |
Comment on lines +251 to +259

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would a client implement a "mentions and calls only" setting for a room?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'd just need a single room-kind rule that mutes anything that isn't caught by override rules. Then the two default override rules for m.rtc.invite and the two default override rules for m.mentions catch both calls and normal mentions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also achieve calls but no mentions otherwise by using the same room-kind rule and disabling the two default override rules for m.mentions.

Or, thirdly, no calls but using the room-kind rule and setting the actions on the two override rules for m.rtc.invite to [] (which means stop processing).

Do you think it would help to spell these combinations out in the proposal?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, sounds plausible, I just wanted to check that it was possible with this push rule design, as I haven't personally studied up on push rules. Unless someone else has the same question, don't think there's a particular need to include this.


Note that in encrypted rooms, the server cannot apply any of the above rules because `m.rtc.invite`
events will be encrypted. In this case, clients need to reapply push rules after decrypting themselves.
This is already the case for other events and push rules.

[`override`]: https://spec.matrix.org/v1.19/client-server-api/#default-override-rules
[`underride`]: https://spec.matrix.org/v1.19/client-server-api/#default-underride-rules

## Potential issues

### Lack of application-specific notifications

This proposal deliberately only covers generic session invites and notifications. Some MatrixRTC
applications might have a need for application specific notification mechanisms though. For instance,
a calling application might want to provide hints for the ringing behaviour in the invite. This
could easily be added on top of this proposal by allowing application specific metadata in a dedicated
`application` object inside of `m.rtc.invite` events. Doing so is left as a task for a future proposal
which can use this MSC as a foundation.

### Lack of feedback

As mentioned above, a ringing UX can be a reasonable choice in certain situations. This proposal
doesn't provide sending clients with a way to know whether their invite is actually ringing
the recipient, however. This could be desirable in order to create an experience akin to classical
phone calls. A future proposal may address this gap, for instance, by introducing ringing
acknowledgements communicated via to-device messages or by designing a more general event delivery
receipt mechanism.

### Lack of push notifications in encrypted mentions-only rooms

In encrypted rooms, the server can see neither the actual type of events nor their `m.mentions`. As a
result, invite events will erroneously be caught by the `room`-kind push rule that is needed to implement
mentions-only rooms which means they don't cause push notifications on mobile clients. As a result,
`m.rtc.invite` notifications can be significantly delayed on mobile clients. This problem is not unique
to MatrixRTC invites and [MSC4028] is an ongoing attempt at solving it generally.

[MSC4028]: https://github.com/matrix-org/matrix-spec-proposals/pull/4028

## Alternatives

### Inferring notifications from membership events

Instead of using dedicated `m.rtc.invite` and `m.rtc.decline` events, invites and declines could also
be inferred from `m.rtc.member` events. Adding the required metadata to these events would likely
overload them though. In comparison, the standalone events introduced in this proposal are more
explicit and form a better foundation for future extensions.

## Security considerations

### Inviting without a valid `m.rtc.member` event

Under this proposal, the sender of `m.rtc.invite` events does not need to be joined to the slot
themselves in order to make the invite valid. This may seem like a potential abuse vector. However,
room members who are able to send `m.rtc.invite` events will commonly also be able to send
`m.rtc.member` events. Thus requiring a member event to send invites doesn't provide additional
protection. The actual access control for sending invites is the power level for sending
`m.rtc.invite` events which room administrators can raise as needed. When power levels allow
sending invitations, users can still mitigate abusive invites by configuring push rules accordingly.

Besides this, requiring the sender to have an `m.rtc.member` event also complicates invite processing.
Particularly on mobile, it would mean that after receiving a push notification, the client would
have to fetch a state event and two different room events, including the information required to
decrypt them, in order to validate the invite. This significantly increases the chance to run into
time limits applied to notification processing by mobile operating systems. While it's possible
to mitigate this by processing invites optimistically while they're being validated in the
background, this introduces further abuse risks.

Lastly, being able to invite users into a MatrixRTC session without being joined oneself, also
acts as a feature and enables integrations such as meeting bots to issue invites without having
to support MatrixRTC themselves.

### Inflating `sender_ts` and/or `lifetime`

A malicious client could send invites with a fake `sender_ts` that lies in the future and/or a
large `lifetime` in an attempt to cause receiving clients to notify or ring their users for extended
periods of time. This is mitigated by the recommendations given earlier, in particular the maximum
allowed difference of 20 seconds between `sender_ts` and `origin_server_ts` and the maximum allowed
`lifetime` of 2 minutes.

### Room-level invites

Using room mentions on `m.rtc.invite` events will notify all users in a room. This has the same
potential for abuse as room mentions on normal messages and can be mitigated by increasing the
power level required to send room notifications.

### Repeated invites

Senders can issue a new invite after a previous invite was declined. This can be abusive. Users can
mitigate this by adapting their push rules, [ignoring] the sender or leaving the room.

[ignoring]: https://spec.matrix.org/v1.18/client-server-api/#ignoring-users

## Appendix: Comparison to [legacy VoIP]

| Feature | Legacy VoIP | MatrixRTC |
| ------- | ----------- | --------- |
| Session invites | ✅ via [`m.call.invite`] events | ✅ via `m.rtc.invite` events |
| Directing invites at specific users | ✅ via `invitee` on [`m.call.invite`] | ✅ via `m.mentions` on `m.rtc.invite` |
| Expiring invites | ⚠️ via `lifetime` on [`m.call.invite`] evaluated against [`age`] which is known to be broken in various homeservers | ✅ via `lifetime` on `m.rtc.invite` evaluated against either `sender_ts` or `origin_server_ts` |
| Inviting without starting a call | ❌ Not possible | ✅ Explicitly allowed if an open slot exists |
| Withdrawing invites | ✅ via [`m.call.hangup`] events | ✅ via empty or redacted `m.rtc.invite` events |
| Declining invites | ✅ via [`m.call.hangup`] events | ✅ via `m.rtc.decline` events |
| Notifications in default rooms | ✅ via `.m.rule.call` push rule | ✅ via `.m.rule.rtc.invite_for_me`, `.m.rule.rtc.invite_for_room` or `.m.rule.rtc.invite` push rules |
| Notifications in mentions-only rooms | ❌ Not possible | ✅ via `.m.rule.rtc.invite_for_me` and `.m.rule.rtc.invite_for_room` push rules |
| Events required to validate session invites | ✅ 1 ([`m.call.invite`]) | ⚠️ 2 (`m.rtc.slot` and `m.rtc.invite`; since `m.rtc.slot` is a state event both can be fetched in the same `/sync`, however) |

[`m.call.invite`]: https://spec.matrix.org/v1.19/client-server-api/#mcallinvite
[`age`]: https://spec.matrix.org/v1.19/client-server-api/#call-event-liveness
[`m.call.hangup`]: https://spec.matrix.org/v1.19/client-server-api/#mcallhangup

## Unstable prefix

| Stable identifier | Purpose | Unstable identifier |
| ----------------- | ------- | --------------------|
| `m.rtc.invite` | Event type | `org.matrix.msc4075.rtc.invite` |
| `m.rtc.decline` | Event type | `org.matrix.msc4075.rtc.decline` |
| `.m.rule.rtc.invite_for_me` | Push rule ID | `.org.matrix.msc4075.rule.rtc.invite_for_me` |
| `.m.rule.rtc.invite_for_room` | Push rule ID | `.org.matrix.msc4075.rule.rtc.invite_for_room` |
| `.m.rule.rtc.invite` | Push rule ID | `.org.matrix.msc4075.rule.rtc.invite` |

## Dependencies

This proposal depends on [MSC4143] and [MSC4354].
Loading