diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 61bfba1f3163cf119e610606f9d89ea67a122171..4092af826336f082c259e6f062ba40d86380852b 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -55,6 +55,7 @@ extension AppEnvironment {
           dbManager: dbManager,
           mainQueue: mainQueue,
           bgQueue: bgQueue,
+          now: Date.init,
           register: {
             RegisterEnvironment(
               messenger: messenger,
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index faa5757c43a4e22a9c8c3b7dd7a99ee83e059a84..985ef3293ca3abdab70532f0d40f99676a1b29fb 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -6,8 +6,10 @@ import ContactsFeature
 import Foundation
 import RegisterFeature
 import UserSearchFeature
+import XCTestDynamicOverlay
 import XXClient
 import XXMessengerClient
+import XXModels
 
 public struct HomeState: Equatable {
   public init(
@@ -47,6 +49,12 @@ public enum HomeAction: Equatable {
     case failure(NSError)
   }
 
+  public enum AuthCallbacks: Equatable {
+    case register
+    case unregister
+    case handle(XXClient.AuthCallbacks.Callback)
+  }
+
   public enum NetworkMonitor: Equatable {
     case start
     case stop
@@ -62,6 +70,7 @@ public enum HomeAction: Equatable {
   }
 
   case messenger(Messenger)
+  case authCallbacks(AuthCallbacks)
   case networkMonitor(NetworkMonitor)
   case deleteAccount(DeleteAccount)
   case didDismissAlert
@@ -81,6 +90,7 @@ public struct HomeEnvironment {
     dbManager: DBManager,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
+    now: @escaping () -> Date,
     register: @escaping () -> RegisterEnvironment,
     contacts: @escaping () -> ContactsEnvironment,
     userSearch: @escaping () -> UserSearchEnvironment
@@ -89,6 +99,7 @@ public struct HomeEnvironment {
     self.dbManager = dbManager
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
+    self.now = now
     self.register = register
     self.contacts = contacts
     self.userSearch = userSearch
@@ -98,6 +109,7 @@ public struct HomeEnvironment {
   public var dbManager: DBManager
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
+  public var now: () -> Date
   public var register: () -> RegisterEnvironment
   public var contacts: () -> ContactsEnvironment
   public var userSearch: () -> UserSearchEnvironment
@@ -109,6 +121,7 @@ extension HomeEnvironment {
     dbManager: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
+    now: XCTUnimplemented("\(Self.self).now", placeholder: Date()),
     register: { .unimplemented },
     contacts: { .unimplemented },
     userSearch: { .unimplemented }
@@ -119,10 +132,12 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
 { state, action, env in
   enum NetworkHealthEffectId {}
   enum NetworkNodesEffectId {}
+  enum AuthCallbacksEffectId {}
 
   switch action {
   case .messenger(.start):
     return .merge(
+      Effect(value: .authCallbacks(.register)),
       Effect(value: .networkMonitor(.stop)),
       Effect.result {
         do {
@@ -160,6 +175,59 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
     state.failure = error.localizedDescription
     return .none
 
+  case .authCallbacks(.register):
+    return Effect.run { subscriber in
+      let handler = AuthCallbacks { callback in
+        subscriber.send(.authCallbacks(.handle(callback)))
+      }
+      let cancellable = env.messenger.registerAuthCallbacks(handler)
+      return AnyCancellable { cancellable.cancel() }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+    .cancellable(id: AuthCallbacksEffectId.self, cancelInFlight: true)
+
+  case .authCallbacks(.unregister):
+    return .cancel(id: AuthCallbacksEffectId.self)
+
+  case .authCallbacks(.handle(.request(let contact, _, _, _))):
+    return .fireAndForget {
+      let db = try env.dbManager.getDB()
+      let contactId = try contact.getId()
+      guard try db.fetchContacts(.init(id: [contactId])).isEmpty else {
+        return
+      }
+      var dbContact = XXModels.Contact(id: contactId)
+      dbContact.marshaled = contact.data
+      dbContact.username = try contact.getFact(.username)?.value
+      dbContact.email = try contact.getFact(.email)?.value
+      dbContact.phone = try contact.getFact(.phone)?.value
+      dbContact.authStatus = .verificationInProgress
+      dbContact.createdAt = env.now()
+      dbContact = try db.saveContact(dbContact)
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .authCallbacks(.handle(.confirm(let contact, _, _, _))):
+    return .fireAndForget {
+      let db = try env.dbManager.getDB()
+      let contactId = try contact.getId()
+      guard var dbContact = try db.fetchContacts(.init(id: [contactId])).first else {
+        return
+      }
+      dbContact.authStatus = .friend
+      dbContact = try db.saveContact(dbContact)
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .authCallbacks(.handle(.reset(let contact, _, _, _))):
+    return .none
+
   case .networkMonitor(.start):
     return .merge(
       Effect.run { subscriber in
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index 89aef4c9f00a7d887f3b1f6bfead0063ade5f263..40cd5a2a7e2fbf4cd586b667d5c2aa9e36392db2 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -21,6 +21,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { messengerDidConnect += 1 }
@@ -32,10 +33,13 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidConnect, 1)
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartUnregistered)) {
       $0.register = RegisterState()
     }
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testMessengerStartRegistered() {
@@ -51,6 +55,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { messengerDidConnect += 1 }
@@ -73,11 +78,14 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidConnect, 1)
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
     store.receive(.networkMonitor(.start))
 
     store.send(.networkMonitor(.stop))
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testRegisterFinished() {
@@ -94,6 +102,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
@@ -118,11 +127,14 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
     store.receive(.networkMonitor(.start))
 
     store.send(.networkMonitor(.stop))
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testMessengerStartFailure() {
@@ -137,14 +149,18 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in throw error }
 
     store.send(.messenger(.start))
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testMessengerStartConnectFailure() {
@@ -159,16 +175,20 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { throw error }
 
     store.send(.messenger(.start))
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testMessengerStartIsRegisteredFailure() {
@@ -183,6 +203,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
@@ -190,10 +211,13 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testMessengerStartLogInFailure() {
@@ -208,6 +232,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
@@ -216,10 +241,13 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testNetworkMonitorStart() {
@@ -491,4 +519,109 @@ final class HomeFeatureTests: XCTestCase {
       $0.contacts = nil
     }
   }
+
+  func testAuthCallbacks() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    let now = Date()
+    var didRegisterCallbacks: [AuthCallbacks] = []
+    var didCancelAuthCallbacks = 0
+    var dbContact: XXModels.Contact?
+    var dbDidSaveContact: [XXModels.Contact] = []
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.now = { now }
+    store.environment.messenger.registerAuthCallbacks.run = { callbacks in
+      didRegisterCallbacks.append(callbacks)
+      return Cancellable { didCancelAuthCallbacks += 1 }
+    }
+    store.environment.dbManager.getDB.run = {
+      var db: Database = .failing
+      db.fetchContacts.run = { _ in [dbContact].compactMap { $0 } }
+      db.saveContact.run = { contact in
+        dbDidSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.authCallbacks(.register))
+
+    XCTAssertNoDifference(didRegisterCallbacks.count, 1)
+
+    var contact = XXClient.Contact.unimplemented("data".data(using: .utf8)!)
+    contact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+    contact.getFactsFromContact.run = { _ in
+      [
+        Fact(type: .username, value: "username"),
+        Fact(type: .email, value: "email"),
+        Fact(type: .phone, value: "phone"),
+      ]
+    }
+
+    dbContact = nil
+    dbDidSaveContact = []
+    didRegisterCallbacks.first?.handle(
+      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    store.receive(.authCallbacks(.handle(
+      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )))
+    XCTAssertNoDifference(dbDidSaveContact, [.init(
+      id: "id".data(using: .utf8)!,
+      marshaled: "data".data(using: .utf8)!,
+      username: "username",
+      email: "email",
+      phone: "phone",
+      authStatus: .verificationInProgress,
+      createdAt: now
+    )])
+
+    dbContact = .init(id: "id".data(using: .utf8)!)
+    dbDidSaveContact = []
+    didRegisterCallbacks.first?.handle(
+      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    store.receive(.authCallbacks(.handle(
+      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )))
+    XCTAssertNoDifference(dbDidSaveContact, [])
+
+    dbContact = .init(id: "id".data(using: .utf8)!)
+    dbDidSaveContact = []
+    didRegisterCallbacks.first?.handle(
+      .confirm(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    store.receive(.authCallbacks(.handle(
+      .confirm(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )))
+    XCTAssertNoDifference(dbDidSaveContact, [.init(
+      id: "id".data(using: .utf8)!,
+      authStatus: .friend,
+      createdAt: dbContact!.createdAt
+    )])
+
+    dbContact = nil
+    dbDidSaveContact = []
+    didRegisterCallbacks.first?.handle(
+      .reset(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    store.receive(.authCallbacks(.handle(
+      .reset(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )))
+    XCTAssertNoDifference(dbDidSaveContact, [])
+
+    store.send(.authCallbacks(.unregister))
+
+    XCTAssertNoDifference(didCancelAuthCallbacks, 1)
+  }
 }