diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b27a8df31ed770979b53ea8297778fd3ab00735..820478f2d1e188117023995f93e0d37b3f973d1e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,10 @@ Versioning](https://semver.org/spec/v2.0.0.html).
 
 - Allow lowercase server names only #11
 
+### Added
+
+- Kick Matrix ID from cancelled orders #9
+
 ## [1.3.0] - 2022-04-19
 
 ### Added
diff --git a/pretix_matrix_inviter/signals.py b/pretix_matrix_inviter/signals.py
index fef84e1a7d3569048f6ca3c5e5ee7e3d6afdd323..1c46966cef06187b5b70385d89dd593b52486504 100644
--- a/pretix_matrix_inviter/signals.py
+++ b/pretix_matrix_inviter/signals.py
@@ -5,12 +5,18 @@ from django.urls import resolve, reverse
 from django.utils.translation import gettext_noop, ugettext_lazy as _
 from i18nfield.strings import LazyI18nString
 from pretix.base.settings import settings_hierarkey
-from pretix.base.signals import logentry_display, order_modified, order_placed
+from pretix.base.signals import (
+    logentry_display,
+    order_canceled,
+    order_expired,
+    order_modified,
+    order_placed,
+)
 from pretix.base.templatetags.rich_text import rich_text_snippet
 from pretix.control.signals import nav_event_settings
 from pretix.presale.signals import question_form_fields
 
-from .tasks import matrix_inviter_invite
+from .tasks import matrix_inviter_invite, matrix_inviter_kick
 
 settings_hierarkey.add_default("matrix_inviter_items", [], list)
 settings_hierarkey.add_default("matrix_inviter_authorization_token", "", str)
@@ -87,6 +93,36 @@ def matrix_inviter_invite_async(sender, order, **kwargs):
         )
 
 
+@receiver(order_canceled, dispatch_uid="matrix_inviter_order_canceled")
+@receiver(order_expired, dispatch_uid="matrix_inviter_order_expired")
+def matrix_inviter_kick_async(sender, order, **kwargs):
+    if (
+        not sender.settings.matrix_inviter_authorization_token
+        and not sender.settings.matrix_inviter_matrix_server
+        and not sender.settings.matrix_inviter_matrix_room
+    ):
+        return
+
+    for order_position in order.positions.all():
+        if str(order_position.item.pk) not in sender.settings.get(
+            "matrix_inviter_items"
+        ):
+            continue
+
+        if not order_position.meta_info_data.get("question_form_data", {}).get(
+            "matrix_inviter_matrix_id"
+        ):
+            continue
+
+        matrix_inviter_kick.apply_async(
+            args=(
+                sender.pk,
+                order.pk,
+                order_position.pk,
+            )
+        )
+
+
 @receiver(nav_event_settings, dispatch_uid="matrix_inviter_nav_settings")
 def navbar_settings(sender, request=None, **kwargs):
     url = resolve(request.path_info)
@@ -115,9 +151,15 @@ def logentry_display(sender, logentry, **kwargs):
         "pretix_matrix_inviter.invite_sent": _(
             "{matrix_id} has been invited to {matrix_room}."
         ),
+        "pretix_matrix_inviter.invite_rescinded": _(
+            "{matrix_id} has been removed from {matrix_room}."
+        ),
         "pretix_matrix_inviter.error": _(
             "There was an error inviting {matrix_id} to {matrix_room}: {error}"
         ),
+        "pretix_matrix_inviter.remove_error": _(
+            "There was an error removing {matrix_id} from {matrix_room}: {error}"
+        ),
     }
     data = json.loads(logentry.data)
 
diff --git a/pretix_matrix_inviter/tasks.py b/pretix_matrix_inviter/tasks.py
index c7cc984c741a48b0d95483713f50a33545e4f4c5..38925e98b4fd1e98726f1e2c62405de71c6818aa 100644
--- a/pretix_matrix_inviter/tasks.py
+++ b/pretix_matrix_inviter/tasks.py
@@ -87,3 +87,77 @@ def matrix_inviter_invite(
                 "matrix_room": room_id,
             },
         )
+
+
+@app.task(
+    base=ProfiledEventTask,
+    bind=True,
+    max_retries=10,
+    retry_backoff=True,
+    retry_backoff_max=3600,
+)
+def matrix_inviter_kick(self, event: int, order: int, order_position: int):
+    order_position = OrderPosition.objects.get(pk=order_position)
+
+    user_matrix_id = order_position.meta_info_data.get("question_form_data", {}).get(
+        "matrix_inviter_matrix_id"
+    )
+
+    if not user_matrix_id:
+        return
+
+    order = Order.objects.get(pk=order)
+    server = event.settings.matrix_inviter_matrix_server
+    token = event.settings.matrix_inviter_authorization_token
+    room_id = matrix_room_id(server, event.settings.matrix_inviter_matrix_room)
+    payload = {"user_id": user_matrix_id}
+
+    try:
+        r = requests.post(
+            "https://{}/_matrix/client/v3/rooms/{}/kick".format(
+                url_quote(server),
+                url_quote(room_id),
+            ),
+            headers={
+                "Authorization": "Bearer {}".format(token),
+            },
+            json=payload,
+        )
+        r.raise_for_status()
+    except (requests.ConnectionError, requests.HTTPError) as e:
+        if r.status_code in (403):
+            order.log_action(
+                "pretix_matrix_inviter.remove_error",
+                data={
+                    "matrix_id": user_matrix_id,
+                    "matrix_room": room_id,
+                    "error": "HTTP Code {} ({})".format(
+                        r.status_code, r.json()["error"]
+                    ),
+                },
+            )
+        else:
+            try:
+                if r.status_code == 429:
+                    backoff = r.json()["retry_after_ms"] / 1000
+                    self.retry(countdown=backoff)
+                else:
+                    self.retry()
+            except MaxRetriesExceededError:
+                order.log_action(
+                    "pretix_matrix_inviter.remove_error",
+                    data={
+                        "matrix_id": user_matrix_id,
+                        "matrix_room": room_id,
+                        "error": "HTTP Code {}".format(r.status_code),
+                    },
+                )
+                raise e
+    else:
+        order.log_action(
+            "pretix_matrix_inviter.invite_rescinded",
+            data={
+                "matrix_id": user_matrix_id,
+                "matrix_room": room_id,
+            },
+        )