diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterMessageListener.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterMessageListener.swift
new file mode 100644
index 0000000000000000000000000000000000000000..666e26345e03b3be620959a37333bba566d991ad
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterMessageListener.swift
@@ -0,0 +1,24 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerRegisterMessageListener {
+  public var run: (Listener) -> Cancellable
+
+  public func callAsFunction(_ listener: Listener) -> Cancellable {
+    run(listener)
+  }
+}
+
+extension MessengerRegisterMessageListener {
+  public static func live(_ env: MessengerEnvironment) -> MessengerRegisterMessageListener {
+    MessengerRegisterMessageListener { listener in
+      env.messageListeners.register(listener)
+    }
+  }
+}
+
+extension MessengerRegisterMessageListener {
+  public static let unimplemented = MessengerRegisterMessageListener(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
index 9c4837748fa35628fdfd87bdbcc0e746d745f77e..b4ff607ed6238450cdbf05aec918376c833060cb 100644
--- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
+++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
@@ -16,6 +16,7 @@ public struct MessengerEnvironment {
   public var loadCMix: LoadCMix
   public var login: Login
   public var lookupUD: LookupUD
+  public var messageListeners: ListenersRegistry
   public var ndfEnvironment: NDFEnvironment
   public var newCMix: NewCMix
   public var newOrLoadUd: NewOrLoadUd
@@ -52,6 +53,7 @@ extension MessengerEnvironment {
       loadCMix: .live,
       login: .live,
       lookupUD: .live,
+      messageListeners: .live(),
       ndfEnvironment: .mainnet,
       newCMix: .live,
       newOrLoadUd: .live,
@@ -83,6 +85,7 @@ extension MessengerEnvironment {
     loadCMix: .unimplemented,
     login: .unimplemented,
     lookupUD: .unimplemented,
+    messageListeners: .unimplemented,
     ndfEnvironment: .unimplemented,
     newCMix: .unimplemented,
     newOrLoadUd: .unimplemented,
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterMessageListenerTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterMessageListenerTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0deedefba66819555f25a883185f52856d7aead1
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterMessageListenerTests.swift
@@ -0,0 +1,34 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerRegisterMessageListenerTests: XCTestCase {
+  func testRegisterAuthCallbacks() {
+    var registeredListeners: [Listener] = []
+    var didHandleMessage: [Message] = []
+    var didCancelRegisteredListener = 0
+
+    var env: MessengerEnvironment = .unimplemented
+    env.messageListeners.register = { listener in
+      registeredListeners.append(listener)
+      return Cancellable { didCancelRegisteredListener += 1 }
+    }
+    let registerMessageListener: MessengerRegisterMessageListener = .live(env)
+    let cancellable = registerMessageListener(Listener { message in
+      didHandleMessage.append(message)
+    })
+
+    XCTAssertEqual(registeredListeners.count, 1)
+
+    registeredListeners.forEach { listener in
+      listener.handle(Message.stub(123))
+    }
+
+    XCTAssertNoDifference(didHandleMessage, [Message.stub(123)])
+
+    cancellable.cancel()
+
+    XCTAssertEqual(didCancelRegisteredListener, 1)
+  }
+}