diff --git a/homeassistant/components/bthome/__init__.py b/homeassistant/components/bthome/__init__.py
index 566609b998b45385db9775b7f5e2ddb17bbfb1da..0031f09bb81b6a06eea5c2f462a6d1f50cab68ab 100644
--- a/homeassistant/components/bthome/__init__.py
+++ b/homeassistant/components/bthome/__init__.py
@@ -2,6 +2,7 @@
 from __future__ import annotations
 
 import logging
+from typing import cast
 
 from bthome_ble import BTHomeBluetoothDeviceData, SensorUpdate
 from bthome_ble.parser import EncryptionScheme
@@ -19,6 +20,7 @@ from homeassistant.helpers.device_registry import (
     DeviceRegistry,
     async_get,
 )
+from homeassistant.helpers.dispatcher import async_dispatcher_send
 
 from .const import (
     BTHOME_BLE_EVENT,
@@ -30,7 +32,7 @@ from .const import (
 )
 from .coordinator import BTHomePassiveBluetoothProcessorCoordinator
 
-PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
+PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.EVENT, Platform.SENSOR]
 
 _LOGGER = logging.getLogger(__name__)
 
@@ -47,7 +49,7 @@ def process_service_info(
     coordinator: BTHomePassiveBluetoothProcessorCoordinator = hass.data[DOMAIN][
         entry.entry_id
     ]
-    discovered_device_classes = coordinator.discovered_device_classes
+    discovered_event_classes = coordinator.discovered_event_classes
     if entry.data.get(CONF_SLEEPY_DEVICE, False) != data.sleepy_device:
         hass.config_entries.async_update_entry(
             entry,
@@ -67,28 +69,35 @@ def process_service_info(
                 sw_version=sensor_device_info.sw_version,
                 hw_version=sensor_device_info.hw_version,
             )
+            # event_class may be postfixed with a number, ie 'button_2'
+            # but if there is only one button then it will be 'button'
             event_class = event.device_key.key
             event_type = event.event_type
 
-            if event_class not in discovered_device_classes:
-                discovered_device_classes.add(event_class)
+            ble_event = BTHomeBleEvent(
+                device_id=device.id,
+                address=address,
+                event_class=event_class,  # ie 'button'
+                event_type=event_type,  # ie 'press'
+                event_properties=event.event_properties,
+            )
+
+            if event_class not in discovered_event_classes:
+                discovered_event_classes.add(event_class)
                 hass.config_entries.async_update_entry(
                     entry,
                     data=entry.data
-                    | {CONF_DISCOVERED_EVENT_CLASSES: list(discovered_device_classes)},
+                    | {CONF_DISCOVERED_EVENT_CLASSES: list(discovered_event_classes)},
+                )
+                async_dispatcher_send(
+                    hass, format_discovered_event_class(address), event_class, ble_event
                 )
 
-            hass.bus.async_fire(
-                BTHOME_BLE_EVENT,
-                dict(
-                    BTHomeBleEvent(
-                        device_id=device.id,
-                        address=address,
-                        event_class=event_class,  # ie 'button'
-                        event_type=event_type,  # ie 'press'
-                        event_properties=event.event_properties,
-                    )
-                ),
+            hass.bus.async_fire(BTHOME_BLE_EVENT, cast(dict, ble_event))
+            async_dispatcher_send(
+                hass,
+                format_event_dispatcher_name(address, event_class),
+                ble_event,
             )
 
     # If payload is encrypted and the bindkey is not verified then we need to reauth
@@ -98,6 +107,16 @@ def process_service_info(
     return update
 
 
+def format_event_dispatcher_name(address: str, event_class: str) -> str:
+    """Format an event dispatcher name."""
+    return f"{DOMAIN}_event_{address}_{event_class}"
+
+
+def format_discovered_event_class(address: str) -> str:
+    """Format a discovered event class."""
+    return f"{DOMAIN}_discovered_event_class_{address}"
+
+
 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     """Set up BTHome Bluetooth from a config entry."""
     address = entry.unique_id
@@ -120,9 +139,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
             hass, entry, data, service_info, device_registry
         ),
         device_data=data,
-        discovered_device_classes=set(
-            entry.data.get(CONF_DISCOVERED_EVENT_CLASSES, [])
-        ),
+        discovered_event_classes=set(entry.data.get(CONF_DISCOVERED_EVENT_CLASSES, [])),
         connectable=False,
         entry=entry,
     )
diff --git a/homeassistant/components/bthome/coordinator.py b/homeassistant/components/bthome/coordinator.py
index bb743be7c7fcd04e61d6b1d8b42880bea3021ecb..837ad58b7c2377b7354a4e570284d9751a036ab7 100644
--- a/homeassistant/components/bthome/coordinator.py
+++ b/homeassistant/components/bthome/coordinator.py
@@ -30,13 +30,13 @@ class BTHomePassiveBluetoothProcessorCoordinator(PassiveBluetoothProcessorCoordi
         mode: BluetoothScanningMode,
         update_method: Callable[[BluetoothServiceInfoBleak], Any],
         device_data: BTHomeBluetoothDeviceData,
-        discovered_device_classes: set[str],
+        discovered_event_classes: set[str],
         entry: ConfigEntry,
         connectable: bool = False,
     ) -> None:
         """Initialize the BTHome Bluetooth Passive Update Processor Coordinator."""
         super().__init__(hass, logger, address, mode, update_method, connectable)
-        self.discovered_device_classes = discovered_device_classes
+        self.discovered_event_classes = discovered_event_classes
         self.device_data = device_data
         self.entry = entry
 
diff --git a/homeassistant/components/bthome/device_trigger.py b/homeassistant/components/bthome/device_trigger.py
index 6bcc1635affbdf629f2ac5f3e406499bf77f0008..834b08ad39db972e8807b9f8affddc64495b5fc8 100644
--- a/homeassistant/components/bthome/device_trigger.py
+++ b/homeassistant/components/bthome/device_trigger.py
@@ -87,6 +87,9 @@ async def async_get_triggers(
         None,
     )
     assert bthome_config_entry is not None
+    event_classes: list[str] = bthome_config_entry.data.get(
+        CONF_DISCOVERED_EVENT_CLASSES, []
+    )
     return [
         {
             # Required fields of TRIGGER_BASE_SCHEMA
@@ -97,10 +100,15 @@ async def async_get_triggers(
             CONF_TYPE: event_class,
             CONF_SUBTYPE: event_type,
         }
-        for event_class in bthome_config_entry.data.get(
-            CONF_DISCOVERED_EVENT_CLASSES, []
+        for event_class in event_classes
+        for event_type in TRIGGERS_BY_EVENT_CLASS.get(
+            event_class.split("_")[0],
+            # If the device has multiple buttons they will have
+            # event classes like button_1 button_2, button_3, etc
+            # but if there is only one button then it will be
+            # button without a number postfix.
+            (),
         )
-        for event_type in TRIGGERS_BY_EVENT_CLASS.get(event_class, [])
     ]
 
 
diff --git a/homeassistant/components/bthome/event.py b/homeassistant/components/bthome/event.py
new file mode 100644
index 0000000000000000000000000000000000000000..39ad66d1d131c639f6e9d52bc02e27f4ee74a377
--- /dev/null
+++ b/homeassistant/components/bthome/event.py
@@ -0,0 +1,133 @@
+"""Support for bthome event entities."""
+from __future__ import annotations
+
+from dataclasses import replace
+
+from homeassistant.components.event import (
+    EventDeviceClass,
+    EventEntity,
+    EventEntityDescription,
+)
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.core import HomeAssistant, callback
+from homeassistant.helpers import device_registry as dr, entity_registry as er
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+
+from . import format_discovered_event_class, format_event_dispatcher_name
+from .const import (
+    DOMAIN,
+    EVENT_CLASS_BUTTON,
+    EVENT_CLASS_DIMMER,
+    EVENT_PROPERTIES,
+    EVENT_TYPE,
+    BTHomeBleEvent,
+)
+from .coordinator import BTHomePassiveBluetoothProcessorCoordinator
+
+DESCRIPTIONS_BY_EVENT_CLASS = {
+    EVENT_CLASS_BUTTON: EventEntityDescription(
+        key=EVENT_CLASS_BUTTON,
+        translation_key="button",
+        event_types=[
+            "press",
+            "double_press",
+            "triple_press",
+            "long_press",
+            "long_double_press",
+            "long_triple_press",
+        ],
+        device_class=EventDeviceClass.BUTTON,
+    ),
+    EVENT_CLASS_DIMMER: EventEntityDescription(
+        key=EVENT_CLASS_DIMMER,
+        translation_key="dimmer",
+        event_types=["rotate_left", "rotate_right"],
+    ),
+}
+
+
+class BTHomeEventEntity(EventEntity):
+    """Representation of a BTHome event entity."""
+
+    _attr_should_poll = False
+    _attr_has_entity_name = True
+
+    def __init__(
+        self,
+        address: str,
+        event_class: str,
+        event: BTHomeBleEvent | None,
+    ) -> None:
+        """Initialise a BTHome event entity."""
+        self._update_signal = format_event_dispatcher_name(address, event_class)
+        # event_class is something like "button" or "dimmer"
+        # and it maybe postfixed with "_1", "_2", "_3", etc
+        # If there is only one button then it will be "button"
+        base_event_class, _, postfix = event_class.partition("_")
+        base_description = DESCRIPTIONS_BY_EVENT_CLASS[base_event_class]
+        self.entity_description = replace(base_description, key=event_class)
+        postfix_name = f" {postfix}" if postfix else ""
+        self._attr_name = f"{base_event_class.title()}{postfix_name}"
+        # Matches logic in PassiveBluetoothProcessorEntity
+        self._attr_device_info = dr.DeviceInfo(
+            identifiers={(DOMAIN, address)},
+            connections={(dr.CONNECTION_BLUETOOTH, address)},
+        )
+        self._attr_unique_id = f"{address}-{event_class}"
+        # If the event is provided then we can set the initial state
+        # since the event itself is likely what triggered the creation
+        # of this entity. We have to do this at creation time since
+        # entities are created dynamically and would otherwise miss
+        # the initial state.
+        if event:
+            self._trigger_event(event[EVENT_TYPE], event[EVENT_PROPERTIES])
+
+    async def async_added_to_hass(self) -> None:
+        """Entity added to hass."""
+        await super().async_added_to_hass()
+        self.async_on_remove(
+            async_dispatcher_connect(
+                self.hass,
+                self._update_signal,
+                self._async_handle_event,
+            )
+        )
+
+    @callback
+    def _async_handle_event(self, event: BTHomeBleEvent) -> None:
+        self._trigger_event(event[EVENT_TYPE], event[EVENT_PROPERTIES])
+        self.async_write_ha_state()
+
+
+async def async_setup_entry(
+    hass: HomeAssistant,
+    entry: ConfigEntry,
+    async_add_entities: AddEntitiesCallback,
+) -> None:
+    """Set up BTHome event."""
+    coordinator: BTHomePassiveBluetoothProcessorCoordinator = hass.data[DOMAIN][
+        entry.entry_id
+    ]
+    address = coordinator.address
+    ent_reg = er.async_get(hass)
+    async_add_entities(
+        # Matches logic in PassiveBluetoothProcessorEntity
+        BTHomeEventEntity(address_event_class[0], address_event_class[2], None)
+        for ent_reg_entry in er.async_entries_for_config_entry(ent_reg, entry.entry_id)
+        if ent_reg_entry.domain == "event"
+        and (address_event_class := ent_reg_entry.unique_id.partition("-"))
+    )
+
+    @callback
+    def _async_discovered_event_class(event_class: str, event: BTHomeBleEvent) -> None:
+        """Handle a newly discovered event class with or without a postfix."""
+        async_add_entities([BTHomeEventEntity(address, event_class, event)])
+
+    entry.async_on_unload(
+        async_dispatcher_connect(
+            hass,
+            format_discovered_event_class(address),
+            _async_discovered_event_class,
+        )
+    )
diff --git a/homeassistant/components/bthome/strings.json b/homeassistant/components/bthome/strings.json
index 39ba3baa3fd05f10b2c79957441f4e493f7f15b7..50c5c7bada6c923ba58c531a8ab6b2cc91117550 100644
--- a/homeassistant/components/bthome/strings.json
+++ b/homeassistant/components/bthome/strings.json
@@ -44,5 +44,33 @@
       "button": "Button \"{subtype}\"",
       "dimmer": "Dimmer \"{subtype}\""
     }
+  },
+  "entity": {
+    "event": {
+      "button": {
+        "state_attributes": {
+          "event_type": {
+            "state": {
+              "press": "Press",
+              "double_press": "Double press",
+              "triple_press": "Triple press",
+              "long_press": "Long press",
+              "long_double_press": "Long double press",
+              "long_triple_press": "Long triple press"
+            }
+          }
+        }
+      },
+      "dimmer": {
+        "state_attributes": {
+          "event_type": {
+            "state": {
+              "rotate_left": "Rotate left",
+              "rotate_right": "Rotate right"
+            }
+          }
+        }
+      }
+    }
   }
 }
diff --git a/tests/components/bthome/test_event.py b/tests/components/bthome/test_event.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6cf3fd49c71bedbe2e768c30a4e2a4347e674e1
--- /dev/null
+++ b/tests/components/bthome/test_event.py
@@ -0,0 +1,116 @@
+"""Test the BTHome events."""
+
+import pytest
+
+from homeassistant.components.bthome.const import DOMAIN
+from homeassistant.components.event import ATTR_EVENT_TYPE
+from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_UNAVAILABLE
+from homeassistant.core import HomeAssistant
+
+from . import make_bthome_v2_adv
+
+from tests.common import MockConfigEntry
+from tests.components.bluetooth import (
+    BluetoothServiceInfoBleak,
+    inject_bluetooth_service_info,
+)
+
+
+@pytest.mark.parametrize(
+    ("mac_address", "advertisement", "bind_key", "result"),
+    [
+        (
+            "A4:C1:38:8D:18:B2",
+            make_bthome_v2_adv(
+                "A4:C1:38:8D:18:B2",
+                b"\x40\x3A\x00\x3A\x01\x3A\x03",
+            ),
+            None,
+            [
+                {
+                    "entity": "event.test_device_18b2_button_2",
+                    ATTR_FRIENDLY_NAME: "Test Device 18B2 Button 2",
+                    ATTR_EVENT_TYPE: "press",
+                },
+                {
+                    "entity": "event.test_device_18b2_button_3",
+                    ATTR_FRIENDLY_NAME: "Test Device 18B2 Button 3",
+                    ATTR_EVENT_TYPE: "triple_press",
+                },
+            ],
+        ),
+        (
+            "A4:C1:38:8D:18:B2",
+            make_bthome_v2_adv(
+                "A4:C1:38:8D:18:B2",
+                b"\x40\x3A\x04",
+            ),
+            None,
+            [
+                {
+                    "entity": "event.test_device_18b2_button",
+                    ATTR_FRIENDLY_NAME: "Test Device 18B2 Button",
+                    ATTR_EVENT_TYPE: "long_press",
+                }
+            ],
+        ),
+    ],
+)
+async def test_v2_events(
+    hass: HomeAssistant,
+    mac_address: str,
+    advertisement: BluetoothServiceInfoBleak,
+    bind_key: str | None,
+    result: list[dict[str, str]],
+) -> None:
+    """Test the different BTHome V2 events."""
+    entry = MockConfigEntry(
+        domain=DOMAIN,
+        unique_id=mac_address,
+        data={"bindkey": bind_key},
+    )
+    entry.add_to_hass(hass)
+
+    assert await hass.config_entries.async_setup(entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert len(hass.states.async_all()) == 0
+
+    inject_bluetooth_service_info(
+        hass,
+        advertisement,
+    )
+    await hass.async_block_till_done()
+    assert len(hass.states.async_all()) == len(result)
+
+    for meas in result:
+        state = hass.states.get(meas["entity"])
+        attributes = state.attributes
+        assert attributes[ATTR_FRIENDLY_NAME] == meas[ATTR_FRIENDLY_NAME]
+        assert attributes[ATTR_EVENT_TYPE] == meas[ATTR_EVENT_TYPE]
+    assert await hass.config_entries.async_unload(entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert await hass.config_entries.async_setup(entry.entry_id)
+    await hass.async_block_till_done()
+
+    # Ensure entities are restored
+    for meas in result:
+        state = hass.states.get(meas["entity"])
+        assert state != STATE_UNAVAILABLE
+
+    # Now inject again
+    inject_bluetooth_service_info(
+        hass,
+        advertisement,
+    )
+    await hass.async_block_till_done()
+    assert len(hass.states.async_all()) == len(result)
+
+    for meas in result:
+        state = hass.states.get(meas["entity"])
+        attributes = state.attributes
+        assert attributes[ATTR_FRIENDLY_NAME] == meas[ATTR_FRIENDLY_NAME]
+        assert attributes[ATTR_EVENT_TYPE] == meas[ATTR_EVENT_TYPE]
+    assert await hass.config_entries.async_unload(entry.entry_id)
+    await hass.async_block_till_done()