diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index 70066bd2241017e30bc4702ec1127cc25fed1688..57081f3ef7c5ae6abcf0cb130aa994d8a6460abc 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -57,6 +57,7 @@ let package = Package(
         .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
         .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
         .product(name: "XXDatabase", package: "client-ios-db"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
         .product(name: "XXModels", package: "client-ios-db"),
       ],
       swiftSettings: swiftSettings
diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandler.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandler.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c6e03cbedb7f1d0020d11a94c6fc0172a5fc5247
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandler.swift
@@ -0,0 +1,49 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+import XXModels
+
+public struct AuthCallbackHandler {
+  public typealias OnError = (Error) -> Void
+
+  public var run: (@escaping OnError) -> Cancellable
+
+  public func callAsFunction(onError: @escaping OnError) -> Cancellable {
+    run(onError)
+  }
+}
+
+extension AuthCallbackHandler {
+  public static func live(
+    messenger: Messenger,
+    handleRequest: AuthCallbackHandlerRequest,
+    handleConfirm: AuthCallbackHandlerConfirm,
+    handleReset: AuthCallbackHandlerReset
+  ) -> AuthCallbackHandler {
+    AuthCallbackHandler { onError in
+      messenger.registerAuthCallbacks(.init { callback in
+        do {
+          switch callback {
+          case .request(let contact, _, _, _):
+            try handleRequest(contact)
+
+          case .confirm(let contact, _, _, _):
+            try handleConfirm(contact)
+
+          case .reset(let contact, _, _, _):
+            try handleReset(contact)
+          }
+        } catch {
+          onError(error)
+        }
+      })
+    }
+  }
+}
+
+extension AuthCallbackHandler {
+  public static let unimplemented = AuthCallbackHandler(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerConfirm.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerConfirm.swift
new file mode 100644
index 0000000000000000000000000000000000000000..2aa6787fff741651d649fb8f88397669b448d92f
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerConfirm.swift
@@ -0,0 +1,31 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXModels
+
+public struct AuthCallbackHandlerConfirm {
+  public var run: (XXClient.Contact) throws -> Void
+
+  public func callAsFunction(_ contact: XXClient.Contact) throws {
+    try run(contact)
+  }
+}
+
+extension AuthCallbackHandlerConfirm {
+  public static func live(db: DBManagerGetDB) -> AuthCallbackHandlerConfirm {
+    AuthCallbackHandlerConfirm { xxContact in
+      let id = try xxContact.getId()
+      guard var dbContact = try db().fetchContacts(.init(id: [id])).first else {
+        return
+      }
+      dbContact.authStatus = .friend
+      dbContact = try db().saveContact(dbContact)
+    }
+  }
+}
+
+extension AuthCallbackHandlerConfirm {
+  public static let unimplemented = AuthCallbackHandlerConfirm(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d8ac3d18a496b64427b293fcc476c341d93353cb
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
@@ -0,0 +1,54 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+import XXModels
+
+public struct AuthCallbackHandlerRequest {
+  public var run: (XXClient.Contact) throws -> Void
+
+  public func callAsFunction(_ contact: XXClient.Contact) throws {
+    try run(contact)
+  }
+}
+
+extension AuthCallbackHandlerRequest {
+  public static func live(
+    db: DBManagerGetDB,
+    messenger: Messenger,
+    now: @escaping () -> Date
+  ) -> AuthCallbackHandlerRequest {
+    AuthCallbackHandlerRequest { xxContact in
+      let id = try xxContact.getId()
+      guard try db().fetchContacts(.init(id: [id])).isEmpty else {
+        return
+      }
+      var dbContact = XXModels.Contact(id: id)
+      dbContact.marshaled = xxContact.data
+      dbContact.username = try xxContact.getFact(.username)?.value
+      dbContact.email = try xxContact.getFact(.email)?.value
+      dbContact.phone = try xxContact.getFact(.phone)?.value
+      dbContact.authStatus = .verificationInProgress
+      dbContact.createdAt = now()
+      dbContact = try db().saveContact(dbContact)
+
+      do {
+        try messenger.waitForNetwork()
+        try messenger.waitForNodes()
+        let verified = try messenger.verifyContact(xxContact)
+        dbContact.authStatus = verified ? .verified : .verificationFailed
+        dbContact = try db().saveContact(dbContact)
+      } catch {
+        dbContact.authStatus = .verificationFailed
+        dbContact = try db().saveContact(dbContact)
+        throw error
+      }
+    }
+  }
+}
+
+extension AuthCallbackHandlerRequest {
+  public static let unimplemented = AuthCallbackHandlerRequest(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift
new file mode 100644
index 0000000000000000000000000000000000000000..729fe13eff8ee42ed1a12ba2ce03d082e5b2cfa8
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift
@@ -0,0 +1,31 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXModels
+
+public struct AuthCallbackHandlerReset {
+  public var run: (XXClient.Contact) throws -> Void
+
+  public func callAsFunction(_ contact: XXClient.Contact) throws {
+    try run(contact)
+  }
+}
+
+extension AuthCallbackHandlerReset {
+  public static func live(db: DBManagerGetDB) -> AuthCallbackHandlerReset {
+    AuthCallbackHandlerReset { xxContact in
+      let id = try xxContact.getId()
+      guard var dbContact = try db().fetchContacts(.init(id: [id])).first else {
+        return
+      }
+      dbContact.authStatus = .stranger
+      dbContact = try db().saveContact(dbContact)
+    }
+  }
+}
+
+extension AuthCallbackHandlerReset {
+  public static let unimplemented = AuthCallbackHandlerReset(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 61bfba1f3163cf119e610606f9d89ea67a122171..5267fcc3f1a42445702386278b8fbeeb600785ae 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -16,6 +16,16 @@ extension AppEnvironment {
     let dbManager = DBManager.live()
     let messengerEnv = MessengerEnvironment.live()
     let messenger = Messenger.live(messengerEnv)
+    let authHandler = AuthCallbackHandler.live(
+      messenger: messenger,
+      handleRequest: .live(
+        db: dbManager.getDB,
+        messenger: messenger,
+        now: Date.init
+      ),
+      handleConfirm: .live(db: dbManager.getDB),
+      handleReset: .live(db: dbManager.getDB)
+    )
     let mainQueue = DispatchQueue.main.eraseToAnyScheduler()
     let bgQueue = DispatchQueue.global(qos: .background).eraseToAnyScheduler()
 
@@ -53,6 +63,7 @@ extension AppEnvironment {
         HomeEnvironment(
           messenger: messenger,
           dbManager: dbManager,
+          authHandler: authHandler,
           mainQueue: mainQueue,
           bgQueue: bgQueue,
           register: {
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index faa5757c43a4e22a9c8c3b7dd7a99ee83e059a84..e28008f39e924af1c5816bb33c9e4dce11798476 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -6,12 +6,15 @@ import ContactsFeature
 import Foundation
 import RegisterFeature
 import UserSearchFeature
+import XCTestDynamicOverlay
 import XXClient
 import XXMessengerClient
+import XXModels
 
 public struct HomeState: Equatable {
   public init(
     failure: String? = nil,
+    authFailure: String? = nil,
     isNetworkHealthy: Bool? = nil,
     networkNodesReport: NodeRegistrationReport? = nil,
     isDeletingAccount: Bool = false,
@@ -21,6 +24,7 @@ public struct HomeState: Equatable {
     userSearch: UserSearchState? = nil
   ) {
     self.failure = failure
+    self.authFailure = authFailure
     self.isNetworkHealthy = isNetworkHealthy
     self.isDeletingAccount = isDeletingAccount
     self.alert = alert
@@ -30,6 +34,7 @@ public struct HomeState: Equatable {
   }
 
   public var failure: String?
+  public var authFailure: String?
   public var isNetworkHealthy: Bool?
   public var networkNodesReport: NodeRegistrationReport?
   public var isDeletingAccount: Bool
@@ -47,6 +52,13 @@ public enum HomeAction: Equatable {
     case failure(NSError)
   }
 
+  public enum AuthHandler: Equatable {
+    case start
+    case stop
+    case failure(NSError)
+    case failureDismissed
+  }
+
   public enum NetworkMonitor: Equatable {
     case start
     case stop
@@ -62,6 +74,7 @@ public enum HomeAction: Equatable {
   }
 
   case messenger(Messenger)
+  case authHandler(AuthHandler)
   case networkMonitor(NetworkMonitor)
   case deleteAccount(DeleteAccount)
   case didDismissAlert
@@ -79,6 +92,7 @@ public struct HomeEnvironment {
   public init(
     messenger: Messenger,
     dbManager: DBManager,
+    authHandler: AuthCallbackHandler,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
     register: @escaping () -> RegisterEnvironment,
@@ -87,6 +101,7 @@ public struct HomeEnvironment {
   ) {
     self.messenger = messenger
     self.dbManager = dbManager
+    self.authHandler = authHandler
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
     self.register = register
@@ -96,6 +111,7 @@ public struct HomeEnvironment {
 
   public var messenger: Messenger
   public var dbManager: DBManager
+  public var authHandler: AuthCallbackHandler
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
   public var register: () -> RegisterEnvironment
@@ -107,6 +123,7 @@ extension HomeEnvironment {
   public static let unimplemented = HomeEnvironment(
     messenger: .unimplemented,
     dbManager: .unimplemented,
+    authHandler: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
     register: { .unimplemented },
@@ -119,10 +136,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: .authHandler(.start)),
       Effect(value: .networkMonitor(.stop)),
       Effect.result {
         do {
@@ -160,6 +179,29 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
     state.failure = error.localizedDescription
     return .none
 
+  case .authHandler(.start):
+    return Effect.run { subscriber in
+      let cancellable = env.authHandler(onError: { error in
+        subscriber.send(.authHandler(.failure(error as NSError)))
+      })
+      return AnyCancellable { cancellable.cancel() }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+    .cancellable(id: AuthCallbacksEffectId.self, cancelInFlight: true)
+
+  case .authHandler(.stop):
+    return .cancel(id: AuthCallbacksEffectId.self)
+
+  case .authHandler(.failure(let error)):
+    state.authFailure = error.localizedDescription
+    return .none
+
+  case .authHandler(.failureDismissed):
+    state.authFailure = nil
+    return .none
+
   case .networkMonitor(.start):
     return .merge(
       Effect.run { subscriber in
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
index f1abfb2b9ebf3d4b2ad4d20aa477b2f49d96fb80..f6a964896e6e67544e693d83cb26378756414b86 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
@@ -15,12 +15,14 @@ public struct HomeView: View {
 
   struct ViewState: Equatable {
     var failure: String?
+    var authFailure: String?
     var isNetworkHealthy: Bool?
     var networkNodesReport: NodeRegistrationReport?
     var isDeletingAccount: Bool
 
     init(state: HomeState) {
       failure = state.failure
+      authFailure = state.authFailure
       isNetworkHealthy = state.isNetworkHealthy
       isDeletingAccount = state.isDeletingAccount
       networkNodesReport = state.networkNodesReport
@@ -31,21 +33,32 @@ public struct HomeView: View {
     WithViewStore(store, observe: ViewState.init) { viewStore in
       NavigationView {
         Form {
-          Section {
-            if let failure = viewStore.failure {
+          if let failure = viewStore.failure {
+            Section {
               Text(failure)
               Button {
                 viewStore.send(.messenger(.start))
               } label: {
                 Text("Retry")
               }
-            }
-          } header: {
-            if viewStore.failure != nil {
+            } header: {
               Text("Error")
             }
           }
 
+          if let authFailure = viewStore.authFailure {
+            Section {
+              Text(authFailure)
+              Button {
+                viewStore.send(.authHandler(.failureDismissed))
+              } label: {
+                Text("Dismiss")
+              }
+            } header: {
+              Text("Auth Error")
+            }
+          }
+
           Section {
             HStack {
               Text("Health")
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerConfirmTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerConfirmTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..1a1b5f19573669403a069bd4e3cecd2e11347e73
--- /dev/null
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerConfirmTests.swift
@@ -0,0 +1,54 @@
+import CustomDump
+import XCTest
+import XXModels
+import XXClient
+@testable import AppCore
+
+final class AuthCallbackHandlerConfirmTests: XCTestCase {
+  func testConfirm() throws {
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let dbContact = XXModels.Contact(
+      id: "id".data(using: .utf8)!,
+      authStatus: .requested
+    )
+    let confirm = AuthCallbackHandlerConfirm.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { query in
+          didFetchContacts.append(query)
+          return [dbContact]
+        }
+        db.saveContact.run = { contact in
+          didSaveContact.append(contact)
+          return contact
+        }
+        return db
+      }
+    )
+    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+
+    try confirm(xxContact)
+
+    XCTAssertNoDifference(didFetchContacts, [.init(id: ["id".data(using: .utf8)!])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.authStatus = .friend
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+  }
+
+  func testConfirmWhenContactNotInDatabase() throws {
+    let confirm = AuthCallbackHandlerConfirm.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { _ in [] }
+        return db
+      }
+    )
+    var contact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    contact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+
+    try confirm(contact)
+  }
+}
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..65786884bfbe0821343509f20bd1f0942998768d
--- /dev/null
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
@@ -0,0 +1,182 @@
+import CustomDump
+import XCTest
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+import XXModels
+@testable import AppCore
+
+final class AuthCallbackHandlerRequestTests: XCTestCase {
+  func testRequestFromNewContact() throws {
+    let now = Date()
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didVerifyContact: [XXClient.Contact] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    var messenger: Messenger = .unimplemented
+    messenger.waitForNetwork.run = { _ in }
+    messenger.waitForNodes.run = { _, _, _, _ in }
+    messenger.verifyContact.run = { contact in
+      didVerifyContact.append(contact)
+      return true
+    }
+
+    let request = AuthCallbackHandlerRequest.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { query in
+          didFetchContacts.append(query)
+          return []
+        }
+        db.saveContact.run = { contact in
+          didSaveContact.append(contact)
+          return contact
+        }
+        return db
+      },
+      messenger: messenger,
+      now: { now }
+    )
+    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+    xxContact.getFactsFromContact.run = { _ in
+      [
+        Fact(type: .username, value: "username"),
+        Fact(type: .email, value: "email"),
+        Fact(type: .phone, value: "phone"),
+      ]
+    }
+
+    try request(xxContact)
+
+    XCTAssertNoDifference(didFetchContacts, [.init(id: ["id".data(using: .utf8)!])])
+    XCTAssertNoDifference(didSaveContact, [
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        username: "username",
+        email: "email",
+        phone: "phone",
+        authStatus: .verificationInProgress,
+        createdAt: now
+      ),
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        username: "username",
+        email: "email",
+        phone: "phone",
+        authStatus: .verified,
+        createdAt: now
+      )
+    ])
+  }
+
+  func testRequestWhenContactInDatabase() throws {
+    let request = AuthCallbackHandlerRequest.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { _ in [.init(id: "id".data(using: .utf8)!)] }
+        return db
+      },
+      messenger: .unimplemented,
+      now: XCTUnimplemented("now", placeholder: Date())
+    )
+    var contact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    contact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+
+    try request(contact)
+  }
+
+  func testRequestFromNewContactVerificationFalse() throws {
+    let now = Date()
+    var didSaveContact: [XXModels.Contact] = []
+
+    var messenger: Messenger = .unimplemented
+    messenger.waitForNetwork.run = { _ in }
+    messenger.waitForNodes.run = { _, _, _, _ in }
+    messenger.verifyContact.run = { _ in false }
+
+    let request = AuthCallbackHandlerRequest.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { query in return [] }
+        db.saveContact.run = { contact in
+          didSaveContact.append(contact)
+          return contact
+        }
+        return db
+      },
+      messenger: messenger,
+      now: { now }
+    )
+    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+    xxContact.getFactsFromContact.run = { _ in [] }
+
+    try request(xxContact)
+
+    XCTAssertNoDifference(didSaveContact, [
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        authStatus: .verificationInProgress,
+        createdAt: now
+      ),
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        authStatus: .verificationFailed,
+        createdAt: now
+      )
+    ])
+  }
+
+  func testRequestFromNewContactVerificationFailure() throws {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+    let now = Date()
+    var didSaveContact: [XXModels.Contact] = []
+
+    var messenger: Messenger = .unimplemented
+    messenger.waitForNetwork.run = { _ in }
+    messenger.waitForNodes.run = { _, _, _, _ in }
+    messenger.verifyContact.run = { _ in throw failure }
+
+    let request = AuthCallbackHandlerRequest.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { query in return [] }
+        db.saveContact.run = { contact in
+          didSaveContact.append(contact)
+          return contact
+        }
+        return db
+      },
+      messenger: messenger,
+      now: { now }
+    )
+    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+    xxContact.getFactsFromContact.run = { _ in [] }
+
+    XCTAssertThrowsError(try request(xxContact)) { error in
+      XCTAssertNoDifference(error as? Failure, failure)
+    }
+
+    XCTAssertNoDifference(didSaveContact, [
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        authStatus: .verificationInProgress,
+        createdAt: now
+      ),
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        authStatus: .verificationFailed,
+        createdAt: now
+      )
+    ])
+  }
+}
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerResetTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerResetTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9a4407bf758a0c0ba922c154d199b758dea701be
--- /dev/null
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerResetTests.swift
@@ -0,0 +1,54 @@
+import CustomDump
+import XCTest
+import XXModels
+import XXClient
+@testable import AppCore
+
+final class AuthCallbackHandlerResetTests: XCTestCase {
+  func testReset() throws {
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let dbContact = XXModels.Contact(
+      id: "id".data(using: .utf8)!,
+      authStatus: .friend
+    )
+    let reset = AuthCallbackHandlerReset.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { query in
+          didFetchContacts.append(query)
+          return [dbContact]
+        }
+        db.saveContact.run = { contact in
+          didSaveContact.append(contact)
+          return contact
+        }
+        return db
+      }
+    )
+    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+
+    try reset(xxContact)
+
+    XCTAssertNoDifference(didFetchContacts, [.init(id: ["id".data(using: .utf8)!])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.authStatus = .stranger
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+  }
+
+  func testResetWhenContactNotInDatabase() throws {
+    let reset = AuthCallbackHandlerReset.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { _ in [] }
+        return db
+      }
+    )
+    var contact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    contact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+
+    try reset(contact)
+  }
+}
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..2eddbbb9e3b9d7524e6ea0e1e65e481cbe921437
--- /dev/null
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerTests.swift
@@ -0,0 +1,114 @@
+import CustomDump
+import XCTest
+import XXClient
+import XXMessengerClient
+@testable import AppCore
+
+final class AuthCallbackHandlerTests: XCTestCase {
+  func testCallbackHandling() throws {
+    struct TestState: Equatable {
+      var didRegisterAuthCallbacks = 0
+      var didCancelAuthCallbacks = 0
+      var didHandleRequest: [Contact] = []
+      var didHandleConfirm: [Contact] = []
+      var didHandleReset: [Contact] = []
+    }
+    var registeredAuthCallbacks: [AuthCallbacks] = []
+    var state = TestState()
+    var expectedState = state
+
+    var messenger: Messenger = .unimplemented
+    messenger.registerAuthCallbacks.run = { callbacks in
+      state.didRegisterAuthCallbacks += 1
+      registeredAuthCallbacks.append(callbacks)
+      return Cancellable { state.didCancelAuthCallbacks += 1 }
+    }
+
+    let handle = AuthCallbackHandler.live(
+      messenger: messenger,
+      handleRequest: .init { state.didHandleRequest.append($0) },
+      handleConfirm: .init { state.didHandleConfirm.append($0) },
+      handleReset: .init { state.didHandleReset.append($0) }
+    )
+
+    var cancellable: Any? = handle(onError: { error in
+      XCTFail("Unexpected error: \(error)")
+    })
+
+    expectedState.didRegisterAuthCallbacks += 1
+    XCTAssertNoDifference(state, expectedState)
+
+    let contact1 = XXClient.Contact.unimplemented("1".data(using: .utf8)!)
+    registeredAuthCallbacks.first?.handle(
+      .request(contact: contact1, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    expectedState.didHandleRequest.append(contact1)
+    XCTAssertNoDifference(state, expectedState)
+
+    let contact2 = XXClient.Contact.unimplemented("2".data(using: .utf8)!)
+    registeredAuthCallbacks.first?.handle(
+      .confirm(contact: contact2, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    expectedState.didHandleConfirm.append(contact2)
+    XCTAssertNoDifference(state, expectedState)
+
+    let contact3 = XXClient.Contact.unimplemented("3".data(using: .utf8)!)
+    registeredAuthCallbacks.first?.handle(
+      .reset(contact: contact3, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    expectedState.didHandleReset.append(contact3)
+    XCTAssertNoDifference(state, expectedState)
+
+    cancellable = nil
+
+    expectedState.didCancelAuthCallbacks += 1
+    XCTAssertNoDifference(state, expectedState)
+
+    _ = cancellable
+  }
+
+  func testCallbackHandlingFailure() {
+    enum Failure: Error, Equatable {
+      case request
+      case confirm
+      case reset
+    }
+    var registeredAuthCallbacks: [AuthCallbacks] = []
+    var errors: [Error] = []
+
+    var messenger: Messenger = .unimplemented
+    messenger.registerAuthCallbacks.run = { callbacks in
+      registeredAuthCallbacks.append(callbacks)
+      return Cancellable {}
+    }
+
+    let handle = AuthCallbackHandler.live(
+      messenger: messenger,
+      handleRequest: .init { _ in throw Failure.request },
+      handleConfirm: .init { _ in throw Failure.confirm },
+      handleReset: .init { _ in throw Failure.reset }
+    )
+
+    let cancellable = handle(onError: { errors.append($0) })
+
+    registeredAuthCallbacks.first?.handle(
+      .request(contact: .unimplemented(Data()), receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+    registeredAuthCallbacks.first?.handle(
+      .confirm(contact: .unimplemented(Data()), receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+    registeredAuthCallbacks.first?.handle(
+      .reset(contact: .unimplemented(Data()), receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    XCTAssertNoDifference(
+      errors.map { $0 as? Failure },
+      [.request, .confirm, .reset]
+    )
+
+    _ = cancellable
+  }
+}
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index 89aef4c9f00a7d887f3b1f6bfead0063ade5f263..f3e5bcf8f787e963820b4a33cd09cb31f4494fd5 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -1,3 +1,4 @@
+import AppCore
 import ComposableArchitecture
 import ContactsFeature
 import RegisterFeature
@@ -21,6 +22,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.authHandler.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 +34,13 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidConnect, 1)
 
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartUnregistered)) {
       $0.register = RegisterState()
     }
+
+    store.send(.authHandler(.stop))
   }
 
   func testMessengerStartRegistered() {
@@ -51,6 +56,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.authHandler.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 +79,13 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidConnect, 1)
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
     store.receive(.networkMonitor(.start))
 
     store.send(.networkMonitor(.stop))
+    store.send(.authHandler(.stop))
   }
 
   func testRegisterFinished() {
@@ -94,6 +102,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.authHandler.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,13 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
     store.receive(.networkMonitor(.start))
 
     store.send(.networkMonitor(.stop))
+    store.send(.authHandler(.stop))
   }
 
   func testMessengerStartFailure() {
@@ -137,14 +148,18 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.authHandler.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in throw error }
 
     store.send(.messenger(.start))
 
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authHandler(.stop))
   }
 
   func testMessengerStartConnectFailure() {
@@ -159,16 +174,20 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.authHandler.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(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authHandler(.stop))
   }
 
   func testMessengerStartIsRegisteredFailure() {
@@ -183,6 +202,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.authHandler.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
@@ -190,10 +210,13 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authHandler(.stop))
   }
 
   func testMessengerStartLogInFailure() {
@@ -208,6 +231,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.authHandler.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
@@ -216,10 +240,13 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authHandler(.stop))
   }
 
   func testNetworkMonitorStart() {
@@ -491,4 +518,45 @@ final class HomeFeatureTests: XCTestCase {
       $0.contacts = nil
     }
   }
+
+  func testAuthCallbacks() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    var didRunAuthHandler = 0
+    var didCancelAuthHandler = 0
+    var authHandlerOnError: [AuthCallbackHandler.OnError] = []
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.authHandler.run = { onError in
+      didRunAuthHandler += 1
+      authHandlerOnError.append(onError)
+      return Cancellable { didCancelAuthHandler += 1 }
+    }
+
+    store.send(.authHandler(.start))
+
+    XCTAssertNoDifference(didRunAuthHandler, 1)
+
+    struct AuthHandlerError: Error { var id: Int }
+    authHandlerOnError.first?(AuthHandlerError(id: 1))
+
+    store.receive(.authHandler(.failure(AuthHandlerError(id: 1) as NSError))) {
+      $0.authFailure = AuthHandlerError(id: 1).localizedDescription
+    }
+
+    store.send(.authHandler(.failureDismissed)) {
+      $0.authFailure = nil
+    }
+
+    store.send(.authHandler(.stop))
+
+    XCTAssertNoDifference(didCancelAuthHandler, 1)
+
+    authHandlerOnError.first?(AuthHandlerError(id: 2))
+  }
 }
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterAuthCallbacks.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterAuthCallbacks.swift
index ad184957aae0c17d24a457d77d047506541eb922..3a8b0a5bcb3585ba2b999710a0c572e6e9186a74 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterAuthCallbacks.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterAuthCallbacks.swift
@@ -19,6 +19,6 @@ extension MessengerRegisterAuthCallbacks {
 
 extension MessengerRegisterAuthCallbacks {
   public static let unimplemented = MessengerRegisterAuthCallbacks(
-    run: XCTUnimplemented("\(Self.self)")
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
   )
 }