diff --git a/Docs/XXMessengerClient.md b/Docs/XXMessengerClient.md
index c2f407c3702f5a04620dd5df035323cda3743ac1..5d4983122ecf46711d8a34f965d65a906fda99c7 100644
--- a/Docs/XXMessengerClient.md
+++ b/Docs/XXMessengerClient.md
@@ -98,6 +98,9 @@ let ud = messenger.ud()
 
 // get Backup:
 let backup = messenger.backup()
+
+// get GroupChat
+let groupChat = messenger.groupChat()
 ```
 
 ## 💾 Backup
@@ -217,3 +220,26 @@ let transferId = try messenger.sendFile(.init(file: file, recipientId: ...)) { i
   }
 }
 ```
+
+## 💬 Group Chat
+
+### Setup
+
+```swift
+// register callbacks:
+let groupRequestsCancellable = messenger.registerGroupRequestHandler(.init { group in
+  // handle group request...
+})
+let groupChatProcessorCancellable = messenger.registerGroupChatProcessor(.init { result in
+  switch result {
+    case .success(let callback):
+    // handle group chat processor callback...
+    
+    case .failure(let error):
+    // handle error...
+  }
+})
+
+// start group chat manager:
+try messenger.startGroupChat()
+```
\ No newline at end of file
diff --git a/Sources/XXClient/Callbacks/GroupChatProcessor.swift b/Sources/XXClient/Callbacks/GroupChatProcessor.swift
index f303aae49186ab75df7a796f735e129027367ec6..a43ec39786fcab1de0d0ceaec978446b57c821c3 100644
--- a/Sources/XXClient/Callbacks/GroupChatProcessor.swift
+++ b/Sources/XXClient/Callbacks/GroupChatProcessor.swift
@@ -2,6 +2,8 @@ import Bindings
 import XCTestDynamicOverlay
 
 public struct GroupChatProcessor {
+  public typealias Result = Swift.Result<Callback, NSError>
+
   public struct Callback: Equatable {
     public init(
       decryptedMessage: GroupChatMessage,
@@ -29,14 +31,14 @@ public struct GroupChatProcessor {
 
   public init(
     name: String = "GroupChatProcessor",
-    handle: @escaping (Result<Callback, NSError>) -> Void
+    handle: @escaping (Result) -> Void
   ) {
     self.name = name
     self.handle = handle
   }
 
   public var name: String
-  public var handle: (Result<Callback, NSError>) -> Void
+  public var handle: (Result) -> Void
 }
 
 extension GroupChatProcessor {
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
index b2b18a4dd28110e05e9490b3e1e8556fbf5e6b7d..392c882068d67b026f70bff9c23ead518e5e8323 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
@@ -19,6 +19,7 @@ extension MessengerDestroy {
           env.sleep(1)
         }
       }
+      env.groupChat.set(nil)
       env.fileTransfer.set(nil)
       env.backup.set(nil)
       env.ud.set(nil)
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterGroupChatProcessor.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterGroupChatProcessor.swift
new file mode 100644
index 0000000000000000000000000000000000000000..2830dd1ae3d2ba28bb6e66d0753abb96b5bb8ad8
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterGroupChatProcessor.swift
@@ -0,0 +1,24 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerRegisterGroupChatProcessor {
+  public var run: (GroupChatProcessor) -> Cancellable
+
+  public func callAsFunction(_ processor: GroupChatProcessor) -> Cancellable {
+    run(processor)
+  }
+}
+
+extension MessengerRegisterGroupChatProcessor {
+  public static func live(_ env: MessengerEnvironment) -> MessengerRegisterGroupChatProcessor {
+    MessengerRegisterGroupChatProcessor { processor in
+      env.groupChatProcessors.register(processor)
+    }
+  }
+}
+
+extension MessengerRegisterGroupChatProcessor {
+  public static let unimplemented = MessengerRegisterGroupChatProcessor(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterGroupRequestHandler.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterGroupRequestHandler.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d298952da0e610cf321643139ef30e076ce6287a
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterGroupRequestHandler.swift
@@ -0,0 +1,24 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerRegisterGroupRequestHandler {
+  public var run: (GroupRequest) -> Cancellable
+
+  public func callAsFunction(_ handler: GroupRequest) -> Cancellable {
+    run(handler)
+  }
+}
+
+extension MessengerRegisterGroupRequestHandler {
+  public static func live(_ env: MessengerEnvironment) -> MessengerRegisterGroupRequestHandler {
+    MessengerRegisterGroupRequestHandler { handler in
+      env.groupRequests.register(handler)
+    }
+  }
+}
+
+extension MessengerRegisterGroupRequestHandler {
+  public static let unimplemented = MessengerRegisterGroupRequestHandler(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStartGroupChat.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartGroupChat.swift
new file mode 100644
index 0000000000000000000000000000000000000000..4ae282b80df87e730b01ea6464aa89e19f34042d
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartGroupChat.swift
@@ -0,0 +1,36 @@
+import XXClient
+import XCTestDynamicOverlay
+
+public struct MessengerStartGroupChat {
+  public enum Error: Swift.Error, Equatable {
+    case notConnected
+  }
+
+  public var run: () throws -> Void
+
+  public func callAsFunction() throws {
+    try run()
+  }
+}
+
+extension MessengerStartGroupChat {
+  public static func live(_ env: MessengerEnvironment) -> MessengerStartGroupChat {
+    MessengerStartGroupChat {
+      guard let e2e = env.e2e.get() else {
+        throw Error.notConnected
+      }
+      let groupChat = try env.newGroupChat(
+        e2eId: e2e.getId(),
+        groupRequest: env.groupRequests.registered(),
+        groupChatProcessor: env.groupChatProcessors.registered()
+      )
+      env.groupChat.set(groupChat)
+    }
+  }
+}
+
+extension MessengerStartGroupChat {
+  public static let unimplemented = MessengerStartGroupChat(
+    run: XCTestDynamicOverlay.unimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift
index 9261d26467400a3f34fc9852c08dd5df1baf0b5d..44b213cb05805352ecffd0d7e07ca6f4880a42a7 100644
--- a/Sources/XXMessengerClient/Messenger/Messenger.swift
+++ b/Sources/XXMessengerClient/Messenger/Messenger.swift
@@ -6,6 +6,7 @@ public struct Messenger {
   public var ud: Stored<UserDiscovery?>
   public var backup: Stored<Backup?>
   public var fileTransfer: Stored<FileTransfer?>
+  public var groupChat: Stored<GroupChat?>
   public var isCreated: MessengerIsCreated
   public var create: MessengerCreate
   public var restoreBackup: MessengerRestoreBackup
@@ -48,6 +49,9 @@ public struct Messenger {
   public var receiveFile: MessengerReceiveFile
   public var trackServices: MessengerTrackServices
   public var getNotificationReports: MessengerGetNotificationReports
+  public var registerGroupRequestHandler: MessengerRegisterGroupRequestHandler
+  public var registerGroupChatProcessor: MessengerRegisterGroupChatProcessor
+  public var startGroupChat: MessengerStartGroupChat
 }
 
 extension Messenger {
@@ -58,6 +62,7 @@ extension Messenger {
       ud: env.ud,
       backup: env.backup,
       fileTransfer: env.fileTransfer,
+      groupChat: env.groupChat,
       isCreated: .live(env),
       create: .live(env),
       restoreBackup: .live(env),
@@ -99,7 +104,10 @@ extension Messenger {
       sendFile: .live(env),
       receiveFile: .live(env),
       trackServices: .live(env),
-      getNotificationReports: .live(env)
+      getNotificationReports: .live(env),
+      registerGroupRequestHandler: .live(env),
+      registerGroupChatProcessor: .live(env),
+      startGroupChat: .live(env)
     )
   }
 }
@@ -111,6 +119,7 @@ extension Messenger {
     ud: .unimplemented(),
     backup: .unimplemented(),
     fileTransfer: .unimplemented(),
+    groupChat: .unimplemented(),
     isCreated: .unimplemented,
     create: .unimplemented,
     restoreBackup: .unimplemented,
@@ -152,6 +161,9 @@ extension Messenger {
     sendFile: .unimplemented,
     receiveFile: .unimplemented,
     trackServices: .unimplemented,
-    getNotificationReports: .unimplemented
+    getNotificationReports: .unimplemented,
+    registerGroupRequestHandler: .unimplemented,
+    registerGroupChatProcessor: .unimplemented,
+    startGroupChat: .unimplemented
   )
 }
diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
index 76707e352ad472aed566118f10963e6bb8600177..dc146e2f89461ac29cc726d44af0753d7f95d1e6 100644
--- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
+++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
@@ -18,6 +18,9 @@ public struct MessengerEnvironment {
   public var getFileTransferParams: GetFileTransferParams
   public var getNotificationsReport: GetNotificationsReport
   public var getSingleUseParams: GetSingleUseParams
+  public var groupChat: Stored<GroupChat?>
+  public var groupChatProcessors: GroupChatProcessorRegistry
+  public var groupRequests: GroupRequestCallbacksRegistry
   public var initFileTransfer: InitFileTransfer
   public var initializeBackup: InitializeBackup
   public var isListeningForMessages: Stored<Bool>
@@ -31,6 +34,7 @@ public struct MessengerEnvironment {
   public var ndfEnvironment: NDFEnvironment
   public var newCMix: NewCMix
   public var newCMixFromBackup: NewCMixFromBackup
+  public var newGroupChat: NewGroupChat
   public var newOrLoadUd: NewOrLoadUd
   public var newUdManagerFromBackup: NewUdManagerFromBackup
   public var passwordStorage: PasswordStorage
@@ -71,6 +75,9 @@ extension MessengerEnvironment {
       getFileTransferParams: .liveDefault,
       getNotificationsReport: .live,
       getSingleUseParams: .liveDefault,
+      groupChat: .inMemory(),
+      groupChatProcessors: .live(),
+      groupRequests: .live(),
       initFileTransfer: .live,
       initializeBackup: .live,
       isListeningForMessages: .inMemory(false),
@@ -84,6 +91,7 @@ extension MessengerEnvironment {
       ndfEnvironment: .mainnet,
       newCMix: .live,
       newCMixFromBackup: .live,
+      newGroupChat: .live,
       newOrLoadUd: .live,
       newUdManagerFromBackup: .live,
       passwordStorage: .keychain,
@@ -119,6 +127,9 @@ extension MessengerEnvironment {
     getFileTransferParams: .unimplemented,
     getNotificationsReport: .unimplemented,
     getSingleUseParams: .unimplemented,
+    groupChat: .unimplemented(),
+    groupChatProcessors: .unimplemented,
+    groupRequests: .unimplemented,
     initFileTransfer: .unimplemented,
     initializeBackup: .unimplemented,
     isListeningForMessages: .unimplemented(placeholder: false),
@@ -132,6 +143,7 @@ extension MessengerEnvironment {
     ndfEnvironment: .unimplemented,
     newCMix: .unimplemented,
     newCMixFromBackup: .unimplemented,
+    newGroupChat: .unimplemented,
     newOrLoadUd: .unimplemented,
     newUdManagerFromBackup: .unimplemented,
     passwordStorage: .unimplemented,
diff --git a/Sources/XXMessengerClient/Utils/GroupChatProcessorRegistry.swift b/Sources/XXMessengerClient/Utils/GroupChatProcessorRegistry.swift
new file mode 100644
index 0000000000000000000000000000000000000000..48390cea619ea81dc76f990b5bce6d6db2de04ca
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/GroupChatProcessorRegistry.swift
@@ -0,0 +1,45 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct GroupChatProcessorRegistry {
+  public var register: (GroupChatProcessor) -> Cancellable
+  public var registered: () -> GroupChatProcessor
+}
+
+extension GroupChatProcessorRegistry {
+  public static func live() -> GroupChatProcessorRegistry {
+    class Registry {
+      var items: [UUID: GroupChatProcessor] = [:]
+    }
+    let registry = Registry()
+    return GroupChatProcessorRegistry(
+      register: { processor in
+        let id = UUID()
+        registry.items[id] = processor
+        return Cancellable { registry.items[id] = nil }
+      },
+      registered: {
+        GroupChatProcessor(
+          name: "GroupChatProcessorRegistry.registered",
+          handle: { result in
+            registry.items.values.forEach { $0.handle(result) }
+          }
+        )
+      }
+    )
+  }
+}
+
+extension GroupChatProcessorRegistry {
+  public static let unimplemented = GroupChatProcessorRegistry(
+    register: XCTestDynamicOverlay.unimplemented(
+      "\(Self.self).register",
+      placeholder: Cancellable {}
+    ),
+    registered: XCTestDynamicOverlay.unimplemented(
+      "\(Self.self).registered",
+      placeholder: .unimplemented
+    )
+  )
+}
diff --git a/Sources/XXMessengerClient/Utils/GroupRequestCallbacksRegistry.swift b/Sources/XXMessengerClient/Utils/GroupRequestCallbacksRegistry.swift
new file mode 100644
index 0000000000000000000000000000000000000000..06aff544a89ca68d345fcead12e43662d692e8a1
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/GroupRequestCallbacksRegistry.swift
@@ -0,0 +1,42 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct GroupRequestCallbacksRegistry {
+  public var register: (GroupRequest) -> Cancellable
+  public var registered: () -> GroupRequest
+}
+
+extension GroupRequestCallbacksRegistry {
+  public static func live() -> GroupRequestCallbacksRegistry {
+    class Registry {
+      var items: [UUID: GroupRequest] = [:]
+    }
+    let registry = Registry()
+    return GroupRequestCallbacksRegistry(
+      register: { groupRequest in
+        let id = UUID()
+        registry.items[id] = groupRequest
+        return Cancellable { registry.items[id] = nil }
+      },
+      registered: {
+        GroupRequest { group in
+          registry.items.values.forEach { $0.handle(group) }
+        }
+      }
+    )
+  }
+}
+
+extension GroupRequestCallbacksRegistry {
+  public static let unimplemented = GroupRequestCallbacksRegistry(
+    register: XCTestDynamicOverlay.unimplemented(
+      "\(Self.self).register",
+      placeholder: Cancellable {}
+    ),
+    registered: XCTestDynamicOverlay.unimplemented(
+      "\(Self.self).registered",
+      placeholder: .unimplemented
+    )
+  )
+}
diff --git a/Sources/XXMessengerClient/Utils/ListenersRegistry.swift b/Sources/XXMessengerClient/Utils/ListenersRegistry.swift
index 6378e3b00295483ec3cc14607ae0a90d8e7e632d..ca311d312866133d317622709d9d1606dcdd4bab 100644
--- a/Sources/XXMessengerClient/Utils/ListenersRegistry.swift
+++ b/Sources/XXMessengerClient/Utils/ListenersRegistry.swift
@@ -31,9 +31,6 @@ extension ListenersRegistry {
 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 }
-    ))
+    registered: XCTUnimplemented("\(Self.self).registered", placeholder: .unimplemented)
   )
 }
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
index 3f87cacb3204fd02488a76dfe2b07f219dba8095..9a784a16cf733eec148b14b30e12e0e83faa90af 100644
--- a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
@@ -10,6 +10,7 @@ final class MessengerDestroyTests: XCTestCase {
     var didStopNetworkFollower = 0
     var didSleep: [TimeInterval] = []
     var didRemoveItem: [String] = []
+    var didSetGroupChat: [GroupChat?] = []
     var didSetFileTransfer: [FileTransfer?] = []
     var didSetBackup: [Backup?] = []
     var didSetUD: [UserDiscovery?] = []
@@ -29,6 +30,7 @@ final class MessengerDestroyTests: XCTestCase {
     }
     env.sleep = { didSleep.append($0) }
     env.storageDir = storageDir
+    env.groupChat.set = { didSetGroupChat.append($0) }
     env.fileTransfer.set = { didSetFileTransfer.append($0) }
     env.backup.set = { didSetBackup.append($0) }
     env.ud.set = { didSetUD.append($0) }
@@ -44,6 +46,7 @@ final class MessengerDestroyTests: XCTestCase {
 
     XCTAssertNoDifference(didStopNetworkFollower, 1)
     XCTAssertNoDifference(didSleep, [1, 1])
+    XCTAssertNoDifference(didSetGroupChat.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetFileTransfer.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetBackup.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetUD.map { $0 == nil }, [true])
@@ -76,6 +79,7 @@ final class MessengerDestroyTests: XCTestCase {
   func testRemoveDirectoryFailure() {
     struct Error: Swift.Error, Equatable {}
     let error = Error()
+    var didSetGroupChat: [GroupChat?] = []
     var didSetFileTransfer: [FileTransfer?] = []
     var didSetBackup: [Backup?] = []
     var didSetUD: [UserDiscovery?] = []
@@ -86,6 +90,7 @@ final class MessengerDestroyTests: XCTestCase {
 
     var env: MessengerEnvironment = .unimplemented
     env.cMix.get = { nil }
+    env.groupChat.set = { didSetGroupChat.append($0) }
     env.fileTransfer.set = { didSetFileTransfer.append($0) }
     env.backup.set = { didSetBackup.append($0) }
     env.ud.set = { didSetUD.append($0) }
@@ -99,6 +104,7 @@ final class MessengerDestroyTests: XCTestCase {
     XCTAssertThrowsError(try destroy()) { err in
       XCTAssertEqual(err as? Error, error)
     }
+    XCTAssertNoDifference(didSetGroupChat.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetFileTransfer.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetBackup.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetUD.map { $0 == nil }, [true])
@@ -113,6 +119,7 @@ final class MessengerDestroyTests: XCTestCase {
     let error = Error()
     let storageDir = "test-storage-dir"
     var didRemoveItem: [String] = []
+    var didSetGroupChat: [GroupChat?] = []
     var didSetFileTransfer: [FileTransfer?] = []
     var didSetBackup: [Backup?] = []
     var didSetUD: [UserDiscovery?] = []
@@ -123,6 +130,7 @@ final class MessengerDestroyTests: XCTestCase {
 
     var env: MessengerEnvironment = .unimplemented
     env.cMix.get = { nil }
+    env.groupChat.set = { didSetGroupChat.append($0) }
     env.fileTransfer.set = { didSetFileTransfer.append($0) }
     env.backup.set = { didSetBackup.append($0) }
     env.ud.set = { didSetUD.append($0) }
@@ -138,6 +146,7 @@ final class MessengerDestroyTests: XCTestCase {
     XCTAssertThrowsError(try destroy()) { err in
       XCTAssertEqual(err as? Error, error)
     }
+    XCTAssertNoDifference(didSetGroupChat.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetFileTransfer.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetBackup.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetUD.map { $0 == nil }, [true])
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterGroupChatProcessorTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterGroupChatProcessorTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..ca517a3c4959b97644659f1e866118789905423c
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterGroupChatProcessorTests.swift
@@ -0,0 +1,34 @@
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerRegisterGroupChatProcessorTests: XCTestCase {
+  func testRegister() {
+    var registered: [GroupChatProcessor] = []
+    var didHandle: [GroupChatProcessor.Result] = []
+    var didCancel = 0
+
+    var env: MessengerEnvironment = .unimplemented
+    env.groupChatProcessors.register = { processor in
+      registered.append(processor)
+      return Cancellable { didCancel += 1 }
+    }
+    let register: MessengerRegisterGroupChatProcessor = .live(env)
+    let cancellable = register(.init { result in
+      didHandle.append(result)
+    })
+
+    XCTAssertEqual(registered.count, 1)
+
+    let result = GroupChatProcessor.Result.success(.stub())
+    registered.forEach { processor in
+      processor.handle(result)
+    }
+
+    XCTAssertEqual(didHandle, [result])
+
+    cancellable.cancel()
+
+    XCTAssertEqual(didCancel, 1)
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterGroupRequestHandlerTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterGroupRequestHandlerTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0bca164c2de9af82b45a33e600e1f2261c892cc4
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterGroupRequestHandlerTests.swift
@@ -0,0 +1,34 @@
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerRegisterGroupRequestHandlerTests: XCTestCase {
+  func testRegister() {
+    var registered: [GroupRequest] = []
+    var didHandle: [Group] = []
+    var didCancel = 0
+
+    var env: MessengerEnvironment = .unimplemented
+    env.groupRequests.register = { handler in
+      registered.append(handler)
+      return Cancellable { didCancel += 1 }
+    }
+    let register: MessengerRegisterGroupRequestHandler = .live(env)
+    let cancellable = register(.init { group in
+      didHandle.append(group)
+    })
+
+    XCTAssertEqual(registered.count, 1)
+
+    let group = Group.stub(1)
+    registered.forEach { handler in
+      handler.handle(group)
+    }
+
+    XCTAssertEqual(didHandle.map { $0.getId() }, [group.getId()])
+
+    cancellable.cancel()
+
+    XCTAssertEqual(didCancel, 1)
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartGroupChatTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartGroupChatTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..aaf31929afab3119079b3c29fe0fbb286dc9191e
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartGroupChatTests.swift
@@ -0,0 +1,44 @@
+import CustomDump
+import XXClient
+import XCTest
+@testable import XXMessengerClient
+
+final class MessengerStartGroupChatTests: XCTestCase {
+  func testStart() throws {
+    var didCreateNewGroupChatWithE2eId: [Int] = []
+    var didSetGroupChat: [GroupChat?] = []
+
+    let e2eId = 123
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { e2eId }
+      return e2e
+    }
+    env.groupRequests.registered = { .unimplemented }
+    env.groupChatProcessors.registered = { .unimplemented }
+    env.newGroupChat.run = { e2eId, _, _ in
+      didCreateNewGroupChatWithE2eId.append(e2eId)
+      return .unimplemented
+    }
+    env.groupChat.set = { groupChat in
+      didSetGroupChat.append(groupChat)
+    }
+    let start: MessengerStartGroupChat = .live(env)
+
+    try start()
+
+    XCTAssertEqual(didCreateNewGroupChatWithE2eId, [e2eId])
+    XCTAssertEqual(didSetGroupChat.map { $0 != nil }, [true])
+  }
+
+  func testStartWithoutE2E() throws {
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = { nil }
+    let start: MessengerStartGroupChat = .live(env)
+
+    XCTAssertThrowsError(try start()) { error in
+      XCTAssertEqual(error as? MessengerStartGroupChat.Error, .notConnected)
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift b/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
index d36ad479f5f865ad5ff2e06656938740054b5676..df7a6271d72996ba549bc4928628124a5dc5c811 100644
--- a/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
+++ b/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
@@ -55,3 +55,36 @@ extension MessageService {
     )
   }
 }
+
+extension Group {
+  static func stub(_ id: Int) -> Group {
+    var group = Group.unimplemented
+    group.getId.run = { "group-\(id)".data(using: .utf8)! }
+    return group
+  }
+}
+
+extension GroupChatMessage {
+  static func stub() -> GroupChatMessage {
+    GroupChatMessage(
+      groupId: "\(Int.random(in: 100...999))".data(using: .utf8)!,
+      senderId: "\(Int.random(in: 100...999))".data(using: .utf8)!,
+      messageId: "\(Int.random(in: 100...999))".data(using: .utf8)!,
+      payload: "\(Int.random(in: 100...999))".data(using: .utf8)!,
+      timestamp: Int64.random(in: 100...999)
+    )
+  }
+}
+
+extension GroupChatProcessor.Callback {
+  static func stub() -> GroupChatProcessor.Callback {
+    GroupChatProcessor.Callback(
+      decryptedMessage: .stub(),
+      msg: "\(Int.random(in: 100...999))".data(using: .utf8)!,
+      receptionId: "\(Int.random(in: 100...999))".data(using: .utf8)!,
+      ephemeralId: Int64.random(in: 100...999),
+      roundId: Int64.random(in: 100...999),
+      roundUrl: "\(Int.random(in: 100...999))"
+    )
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Utils/GroupChatProcessorRegistryTests.swift b/Tests/XXMessengerClientTests/Utils/GroupChatProcessorRegistryTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..85ab46d913d117adad8bd0b9140f0f299c3a7a7d
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Utils/GroupChatProcessorRegistryTests.swift
@@ -0,0 +1,48 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class GroupChatProcessorRegistryTests: XCTestCase {
+  func testRegistry() {
+    var firstProcessorDidHandle: [GroupChatProcessor.Result] = []
+    var secondProcessorDidHandle: [GroupChatProcessor.Result] = []
+
+    let firstProcessor = GroupChatProcessor(
+      name: "first",
+      handle: { firstProcessorDidHandle.append($0) }
+    )
+    let secondProcessor = GroupChatProcessor(
+      name: "second",
+      handle: { secondProcessorDidHandle.append($0) }
+    )
+    let registry: GroupChatProcessorRegistry = .live()
+    let registeredProcessors = registry.registered()
+    let firstProcessorCancellable = registry.register(firstProcessor)
+    let secondProcessorCancellable = registry.register(secondProcessor)
+
+    let firstResult = GroupChatProcessor.Result.success(.stub())
+    registeredProcessors.handle(firstResult)
+
+    XCTAssertNoDifference(firstProcessorDidHandle, [firstResult])
+    XCTAssertNoDifference(secondProcessorDidHandle, [firstResult])
+
+    firstProcessorDidHandle = []
+    secondProcessorDidHandle = []
+    firstProcessorCancellable.cancel()
+    let secondResult = GroupChatProcessor.Result.success(.stub())
+    registeredProcessors.handle(secondResult)
+
+    XCTAssertNoDifference(firstProcessorDidHandle, [])
+    XCTAssertNoDifference(secondProcessorDidHandle, [secondResult])
+
+    firstProcessorDidHandle = []
+    secondProcessorDidHandle = []
+    secondProcessorCancellable.cancel()
+    let thirdResult = GroupChatProcessor.Result.success(.stub())
+    registeredProcessors.handle(thirdResult)
+
+    XCTAssertNoDifference(firstProcessorDidHandle, [])
+    XCTAssertNoDifference(secondProcessorDidHandle, [])
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Utils/GroupRequestCallbacksRegistryTests.swift b/Tests/XXMessengerClientTests/Utils/GroupRequestCallbacksRegistryTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..386f9d46ad5f95d276d8f812452b4a95f3a772ae
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Utils/GroupRequestCallbacksRegistryTests.swift
@@ -0,0 +1,46 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class GroupRequestCallbacksRegistryTests: XCTestCase {
+  func testRegistry() {
+    var firstCallbackDidHandle: [Group] = []
+    var secondCallbackDidHandle: [Group] = []
+
+    let firstCallback = GroupRequest { group in
+      firstCallbackDidHandle.append(group)
+    }
+    let secondCallback = GroupRequest { group in
+      secondCallbackDidHandle.append(group)
+    }
+    let registry: GroupRequestCallbacksRegistry = .live()
+    let registeredCallbacks = registry.registered()
+    let firstCallbackCancellable = registry.register(firstCallback)
+    let secondCallbackCancellable = registry.register(secondCallback)
+
+    let firstGroup = Group.stub(1)
+    registeredCallbacks.handle(firstGroup)
+
+    XCTAssertNoDifference(firstCallbackDidHandle.map { $0.getId() }, [firstGroup.getId()])
+    XCTAssertNoDifference(secondCallbackDidHandle.map { $0.getId() }, [firstGroup.getId()])
+
+    firstCallbackDidHandle = []
+    secondCallbackDidHandle = []
+    firstCallbackCancellable.cancel()
+    let secondGroup = Group.stub(2)
+    registeredCallbacks.handle(secondGroup)
+
+    XCTAssertNoDifference(firstCallbackDidHandle.map { $0.getId() }, [])
+    XCTAssertNoDifference(secondCallbackDidHandle.map { $0.getId() }, [secondGroup.getId()])
+
+    firstCallbackDidHandle = []
+    secondCallbackDidHandle = []
+    secondCallbackCancellable.cancel()
+    let thirdGroup = Group.stub(3)
+    registeredCallbacks.handle(thirdGroup)
+
+    XCTAssertNoDifference(firstCallbackDidHandle.map { $0.getId() }, [])
+    XCTAssertNoDifference(secondCallbackDidHandle.map { $0.getId() }, [])
+  }
+}