diff --git a/Sources/XXMessengerClient/Utils/ListenersRegistry.swift b/Sources/XXMessengerClient/Utils/ListenersRegistry.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6378e3b00295483ec3cc14607ae0a90d8e7e632d
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/ListenersRegistry.swift
@@ -0,0 +1,39 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct ListenersRegistry {
+  public var register: (Listener) -> Cancellable
+  public var registered: () -> Listener
+}
+
+extension ListenersRegistry {
+  public static func live() -> ListenersRegistry {
+    class Registry {
+      var listeners: [UUID: Listener] = [:]
+    }
+    let registry = Registry()
+    return ListenersRegistry(
+      register: { listener in
+        let id = UUID()
+        registry.listeners[id] = listener
+        return Cancellable { registry.listeners[id] = nil }
+      },
+      registered: {
+        Listener(name: "listeners-registry") { message in
+          registry.listeners.values.forEach { $0.handle(message) }
+        }
+      }
+    )
+  }
+}
+
+extension ListenersRegistry {
+  public static let unimplemented = ListenersRegistry(
+    register: XCTUnimplemented("\(Self.self).register", placeholder: Cancellable {}),
+    registered: XCTUnimplemented("\(Self.self).registered", placeholder: Listener(
+      name: "unimplemented",
+      handle: { _ in }
+    ))
+  )
+}
diff --git a/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift b/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e10bea4a6298544c2e4c4d01ba267a00d2dcc167
--- /dev/null
+++ b/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift
@@ -0,0 +1,18 @@
+import XXClient
+
+extension Message {
+  static func stub(_ stubId: Int) -> Message {
+    .init(
+      messageType: stubId,
+      id: "id-\(stubId)".data(using: .utf8)!,
+      payload: "payload-\(stubId)".data(using: .utf8)!,
+      sender: "sender-\(stubId)".data(using: .utf8)!,
+      recipientId: "recipientId-\(stubId)".data(using: .utf8)!,
+      ephemeralId: stubId,
+      timestamp: stubId,
+      encrypted: stubId % 2 == 0,
+      roundId: stubId,
+      roundURL: "roundURL-\(stubId)"
+    )
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Utils/ListenersRegistryTests.swift b/Tests/XXMessengerClientTests/Utils/ListenersRegistryTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..763f92b69aea1b2be78c47a31250ab8a9229fe22
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Utils/ListenersRegistryTests.swift
@@ -0,0 +1,43 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class ListenersRegistryTestsTests: XCTestCase {
+  func testRegistry() {
+    var firstListenerDidHandle: [Message] = []
+    var secondListenerDidHandle: [Message] = []
+
+    let firstListener = Listener { message in
+      firstListenerDidHandle.append(message)
+    }
+    let secondListener = Listener { message in
+      secondListenerDidHandle.append(message)
+    }
+    let listenersRegistry: ListenersRegistry = .live()
+    let registeredListeners = listenersRegistry.registered()
+    let firstListenerCancellable = listenersRegistry.register(firstListener)
+    let secondListenerCancellable = listenersRegistry.register(secondListener)
+
+    let firstMessage = Message.stub(1)
+    registeredListeners.handle(firstMessage)
+
+    XCTAssertNoDifference(firstListenerDidHandle, [firstMessage])
+    XCTAssertNoDifference(secondListenerDidHandle, [firstMessage])
+
+    firstListenerCancellable.cancel()
+    let secondMessage = Message.stub(2)
+    registeredListeners.handle(secondMessage)
+
+    XCTAssertNoDifference(firstListenerDidHandle, [firstMessage])
+    XCTAssertNoDifference(secondListenerDidHandle, [firstMessage, secondMessage])
+
+    secondListenerCancellable.cancel()
+
+    let thirdMessage = Message.stub(3)
+    registeredListeners.handle(thirdMessage)
+
+    XCTAssertNoDifference(firstListenerDidHandle, [firstMessage])
+    XCTAssertNoDifference(secondListenerDidHandle, [firstMessage, secondMessage])
+  }
+}