diff --git a/changelog.d/17964.feature b/changelog.d/17964.feature
new file mode 100644
index 0000000000000000000000000000000000000000..e2ae566eb98fd8dfba4b4eb0d9200828de5b2330
--- /dev/null
+++ b/changelog.d/17964.feature
@@ -0,0 +1 @@
+Support stable account suspension from [MSC3823](https://github.com/matrix-org/matrix-spec-proposals/pull/3823).
\ No newline at end of file
diff --git a/synapse/api/errors.py b/synapse/api/errors.py
index 71e4bb49716de446d2d2aa4382a13eb54d171bbe..21989b6e0e83f04014488d5bd0d873917e65caef 100644
--- a/synapse/api/errors.py
+++ b/synapse/api/errors.py
@@ -100,8 +100,9 @@ class Codes(str, Enum):
     # The account has been suspended on the server.
     # By opposition to `USER_DEACTIVATED`, this is a reversible measure
     # that can possibly be appealed and reverted.
-    # Part of MSC3823.
-    USER_ACCOUNT_SUSPENDED = "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
+    # Introduced by MSC3823
+    # https://github.com/matrix-org/matrix-spec-proposals/pull/3823
+    USER_ACCOUNT_SUSPENDED = "M_USER_SUSPENDED"
 
     BAD_ALIAS = "M_BAD_ALIAS"
     # For restricted join rules.
diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py
index 57ac27697f4bc4ab72910c34af6cae4355478f8f..eb8d967e70901d14e4f67f313f082f3e05b968e8 100644
--- a/synapse/config/experimental.py
+++ b/synapse/config/experimental.py
@@ -436,10 +436,6 @@ class ExperimentalConfig(Config):
                 ("experimental", "msc4108_delegation_endpoint"),
             )
 
-        self.msc3823_account_suspension = experimental.get(
-            "msc3823_account_suspension", False
-        )
-
         # MSC4151: Report room API (Client-Server API)
         self.msc4151_enabled: bool = experimental.get("msc4151_enabled", False)
 
diff --git a/synapse/rest/admin/__init__.py b/synapse/rest/admin/__init__.py
index 4db89756747b017c5cfd4a71e248ac5b6da78065..c01282a43ef9892aa0e6f1568617cf7daba0afd9 100644
--- a/synapse/rest/admin/__init__.py
+++ b/synapse/rest/admin/__init__.py
@@ -332,8 +332,7 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
     BackgroundUpdateRestServlet(hs).register(http_server)
     BackgroundUpdateStartJobRestServlet(hs).register(http_server)
     ExperimentalFeaturesRestServlet(hs).register(http_server)
-    if hs.config.experimental.msc3823_account_suspension:
-        SuspendAccountRestServlet(hs).register(http_server)
+    SuspendAccountRestServlet(hs).register(http_server)
 
 
 def register_servlets_for_client_rest_resource(
diff --git a/tests/rest/admin/test_user.py b/tests/rest/admin/test_user.py
index fdb8fafa0e84b880ddfd2cbe9ae2a9db67cf392e..9a0e90208da6c4ad1576b08a788bc5453bfed8a1 100644
--- a/tests/rest/admin/test_user.py
+++ b/tests/rest/admin/test_user.py
@@ -5031,7 +5031,6 @@ class UserSuspensionTestCase(unittest.HomeserverTestCase):
 
         self.store = hs.get_datastores().main
 
-    @override_config({"experimental_features": {"msc3823_account_suspension": True}})
     def test_suspend_user(self) -> None:
         # test that suspending user works
         channel = self.make_request(
diff --git a/tests/rest/client/test_rooms.py b/tests/rest/client/test_rooms.py
index 07600418ed867f1d80e5366e7af5f0bfa6b901b9..4cf1a3dc51908c409513b0e9eb0cbcac45938dcb 100644
--- a/tests/rest/client/test_rooms.py
+++ b/tests/rest/client/test_rooms.py
@@ -1337,17 +1337,13 @@ class RoomJoinTestCase(RoomBase):
             "POST", f"/join/{self.room1}", access_token=self.tok2
         )
         self.assertEqual(channel.code, 403)
-        self.assertEqual(
-            channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
-        )
+        self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
 
         channel = self.make_request(
             "POST", f"/rooms/{self.room1}/join", access_token=self.tok2
         )
         self.assertEqual(channel.code, 403)
-        self.assertEqual(
-            channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
-        )
+        self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
 
     def test_suspended_user_cannot_knock_on_room(self) -> None:
         # set the user as suspended
@@ -1361,9 +1357,7 @@ class RoomJoinTestCase(RoomBase):
             shorthand=False,
         )
         self.assertEqual(channel.code, 403)
-        self.assertEqual(
-            channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
-        )
+        self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
 
     def test_suspended_user_cannot_invite_to_room(self) -> None:
         # set the user as suspended
@@ -1376,9 +1370,7 @@ class RoomJoinTestCase(RoomBase):
             access_token=self.tok1,
             content={"user_id": self.user2},
         )
-        self.assertEqual(
-            channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
-        )
+        self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
 
 
 class RoomAppserviceTsParamTestCase(unittest.HomeserverTestCase):
@@ -4011,9 +4003,7 @@ class UserSuspensionTests(unittest.HomeserverTestCase):
             access_token=self.tok1,
             content={"body": "hello", "msgtype": "m.text"},
         )
-        self.assertEqual(
-            channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
-        )
+        self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
 
     def test_suspended_user_cannot_change_profile_data(self) -> None:
         # set the user as suspended
@@ -4026,9 +4016,7 @@ class UserSuspensionTests(unittest.HomeserverTestCase):
             content={"avatar_url": "mxc://matrix.org/wefh34uihSDRGhw34"},
             shorthand=False,
         )
-        self.assertEqual(
-            channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
-        )
+        self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
 
         channel2 = self.make_request(
             "PUT",
@@ -4037,9 +4025,7 @@ class UserSuspensionTests(unittest.HomeserverTestCase):
             content={"displayname": "something offensive"},
             shorthand=False,
         )
-        self.assertEqual(
-            channel2.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
-        )
+        self.assertEqual(channel2.json_body["errcode"], "M_USER_SUSPENDED")
 
     def test_suspended_user_cannot_redact_messages_other_than_their_own(self) -> None:
         # first user sends message
@@ -4073,9 +4059,7 @@ class UserSuspensionTests(unittest.HomeserverTestCase):
             content={"reason": "bogus"},
             shorthand=False,
         )
-        self.assertEqual(
-            channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
-        )
+        self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
 
         # but can redact their own
         channel = self.make_request(