diff --git a/changelog.d/17975.feature b/changelog.d/17975.feature
new file mode 100644
index 0000000000000000000000000000000000000000..48f41bddad03043962799d2fbe23007cc478002a
--- /dev/null
+++ b/changelog.d/17975.feature
@@ -0,0 +1 @@
+[MSC4076](https://github.com/matrix-org/matrix-spec-proposals/pull/4076): Add `disable_badge_count` to pusher configuration.
diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py
index 3411179a2a317a73aa85d9bd8b377ec006f2a5ca..57ac27697f4bc4ab72910c34af6cae4355478f8f 100644
--- a/synapse/config/experimental.py
+++ b/synapse/config/experimental.py
@@ -448,3 +448,6 @@ class ExperimentalConfig(Config):
 
         # MSC4222: Adding `state_after` to sync v2
         self.msc4222_enabled: bool = experimental.get("msc4222_enabled", False)
+
+        # MSC4076: Add `disable_badge_count`` to pusher configuration
+        self.msc4076_enabled: bool = experimental.get("msc4076_enabled", False)
diff --git a/synapse/push/httppusher.py b/synapse/push/httppusher.py
index dd9b64d6effbc3f69d337ac8e221ba5c0b2a6df8..69790ecab545624d7bbd382f9760b9d39c961165 100644
--- a/synapse/push/httppusher.py
+++ b/synapse/push/httppusher.py
@@ -127,6 +127,11 @@ class HttpPusher(Pusher):
         if self.data is None:
             raise PusherConfigException("'data' key can not be null for HTTP pusher")
 
+        # Check if badge counts should be disabled for this push gateway
+        self.disable_badge_count = self.hs.config.experimental.msc4076_enabled and bool(
+            self.data.get("org.matrix.msc4076.disable_badge_count", False)
+        )
+
         self.name = "%s/%s/%s" % (
             pusher_config.user_name,
             pusher_config.app_id,
@@ -461,9 +466,10 @@ class HttpPusher(Pusher):
             content: JsonDict = {
                 "event_id": event.event_id,
                 "room_id": event.room_id,
-                "counts": {"unread": badge},
                 "prio": priority,
             }
+            if not self.disable_badge_count:
+                content["counts"] = {"unread": badge}
             # event_id_only doesn't include the tweaks, so override them.
             tweaks = {}
         else:
@@ -478,11 +484,11 @@ class HttpPusher(Pusher):
                 "type": event.type,
                 "sender": event.user_id,
                 "prio": priority,
-                "counts": {
-                    "unread": badge,
-                    # 'missed_calls': 2
-                },
             }
+            if not self.disable_badge_count:
+                content["counts"] = {
+                    "unread": badge,
+                }
             if event.type == "m.room.member" and event.is_state():
                 content["membership"] = event.content["membership"]
                 content["user_is_target"] = event.state_key == self.user_id
diff --git a/tests/push/test_http.py b/tests/push/test_http.py
index bcca472617e3fc9c52acc085ed3308c71b54ccd3..5c235bbe5363a6cceccbb83b27f2807587635cae 100644
--- a/tests/push/test_http.py
+++ b/tests/push/test_http.py
@@ -17,9 +17,11 @@
 # [This file includes modifications made by New Vector Limited]
 #
 #
-from typing import Any, List, Tuple
+from typing import Any, Dict, List, Tuple
 from unittest.mock import Mock
 
+from parameterized import parameterized
+
 from twisted.internet.defer import Deferred
 from twisted.test.proto_helpers import MemoryReactor
 
@@ -1085,3 +1087,83 @@ class HTTPPusherTests(HomeserverTestCase):
             self.pump()
 
         self.assertEqual(len(self.push_attempts), 11)
+
+    @parameterized.expand(
+        [
+            # Badge count disabled
+            (True, True),
+            (True, False),
+            # Badge count enabled
+            (False, True),
+            (False, False),
+        ]
+    )
+    @override_config({"experimental_features": {"msc4076_enabled": True}})
+    def test_msc4076_badge_count(
+        self, disable_badge_count: bool, event_id_only: bool
+    ) -> None:
+        # Register the user who gets notified
+        user_id = self.register_user("user", "pass")
+        access_token = self.login("user", "pass")
+
+        # Register the user who sends the message
+        other_user_id = self.register_user("otheruser", "pass")
+        other_access_token = self.login("otheruser", "pass")
+
+        # Register the pusher with disable_badge_count set to True
+        user_tuple = self.get_success(
+            self.hs.get_datastores().main.get_user_by_access_token(access_token)
+        )
+        assert user_tuple is not None
+        device_id = user_tuple.device_id
+
+        # Set the push data dict based on test input parameters
+        push_data: Dict[str, Any] = {
+            "url": "http://example.com/_matrix/push/v1/notify",
+        }
+        if disable_badge_count:
+            push_data["org.matrix.msc4076.disable_badge_count"] = True
+        if event_id_only:
+            push_data["format"] = "event_id_only"
+
+        self.get_success(
+            self.hs.get_pusherpool().add_or_update_pusher(
+                user_id=user_id,
+                device_id=device_id,
+                kind="http",
+                app_id="m.http",
+                app_display_name="HTTP Push Notifications",
+                device_display_name="pushy push",
+                pushkey="a@example.com",
+                lang=None,
+                data=push_data,
+            )
+        )
+
+        # Create a room
+        room = self.helper.create_room_as(user_id, tok=access_token)
+
+        # The other user joins
+        self.helper.join(room=room, user=other_user_id, tok=other_access_token)
+
+        # The other user sends a message
+        self.helper.send(room, body="Hi!", tok=other_access_token)
+
+        # Advance time a bit, so the pusher will register something has happened
+        self.pump()
+
+        # One push was attempted to be sent
+        self.assertEqual(len(self.push_attempts), 1)
+        self.assertEqual(
+            self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
+        )
+
+        if disable_badge_count:
+            # Verify that the notification DOESN'T contain a counts field
+            self.assertNotIn("counts", self.push_attempts[0][2]["notification"])
+        else:
+            # Ensure that the notification DOES contain a counts field
+            self.assertIn("counts", self.push_attempts[0][2]["notification"])
+            self.assertEqual(
+                self.push_attempts[0][2]["notification"]["counts"]["unread"], 1
+            )