diff --git a/native/android/app/CMakeLists.txt b/native/android/app/CMakeLists.txt
--- a/native/android/app/CMakeLists.txt
+++ b/native/android/app/CMakeLists.txt
@@ -119,6 +119,7 @@
   "./src/cpp/AESCrypto.cpp"
   "./src/cpp/CommServicesAuthMetadataEmitter.cpp"
   "./src/cpp/CommMMKV.cpp"
+  "./src/cpp/NotificationsInboundKeysProvider.cpp"
 )
 
 list(APPEND GENERATED_NATIVE_CODE
diff --git a/native/android/app/src/cpp/NotificationsInboundKeysProvider.cpp b/native/android/app/src/cpp/NotificationsInboundKeysProvider.cpp
new file mode 100644
--- /dev/null
+++ b/native/android/app/src/cpp/NotificationsInboundKeysProvider.cpp
@@ -0,0 +1,32 @@
+#include "jniHelpers.h"
+#include <Notifications/BackgroundDataStorage/NotificationsInboundKeysProvider.h>
+#include <fbjni/fbjni.h>
+
+using namespace facebook::jni;
+
+class NotificationsInboundKeysProviderJavaClass
+    : public JavaClass<NotificationsInboundKeysProviderJavaClass> {
+public:
+  static auto constexpr kJavaDescriptor =
+      "Lapp/comm/android/fbjni/NotificationsInboundKeysProvider;";
+
+  static std::string getNotifsInboundKeysForDeviceID(std::string deviceID) {
+    static const auto cls = javaClassStatic();
+    static auto method = cls->getStaticMethod<JString(std::string)>(
+        "getNotifsInboundKeysForDeviceID");
+    const auto result = method(cls, deviceID);
+    return result->toStdString();
+  }
+};
+
+namespace comm {
+std::string NotificationsInboundKeysProvider::getNotifsInboundKeysForDeviceID(
+    const std::string &deviceID) {
+  std::string result;
+  NativeAndroidAccessProvider::runTask([&]() {
+    result = NotificationsInboundKeysProviderJavaClass::
+        getNotifsInboundKeysForDeviceID(deviceID);
+  });
+  return result;
+}
+} // namespace comm
diff --git a/native/android/app/src/main/java/app/comm/android/commservices/CommAndroidServicesClient.java b/native/android/app/src/main/java/app/comm/android/commservices/CommAndroidServicesClient.java
--- a/native/android/app/src/main/java/app/comm/android/commservices/CommAndroidServicesClient.java
+++ b/native/android/app/src/main/java/app/comm/android/commservices/CommAndroidServicesClient.java
@@ -34,6 +34,9 @@
           .callTimeout(NOTIF_PROCESSING_TIME_LIMIT_SECONDS, TimeUnit.SECONDS)
           .build();
 
+  public static final CommAndroidServicesClient instance =
+      new CommAndroidServicesClient();
+
   public static final String BLOB_SERVICE_URL = BuildConfig.DEBUG
       ? "https://blob.staging.commtechnologies.org"
       : "https://blob.commtechnologies.org";
@@ -43,6 +46,10 @@
   public static final String BLOB_HASH_KEY = "blob_hash";
   public static final String BLOB_HOLDER_KEY = "holder";
 
+  public static CommAndroidServicesClient getInstance() {
+    return CommAndroidServicesClient.instance;
+  }
+
   public byte[] getBlobSync(String blobHash) throws IOException, JSONException {
     String authToken = getAuthToken();
     Request request = new Request.Builder()
diff --git a/native/android/app/src/main/java/app/comm/android/fbjni/NotificationsInboundKeysProvider.java b/native/android/app/src/main/java/app/comm/android/fbjni/NotificationsInboundKeysProvider.java
new file mode 100644
--- /dev/null
+++ b/native/android/app/src/main/java/app/comm/android/fbjni/NotificationsInboundKeysProvider.java
@@ -0,0 +1,24 @@
+package app.comm.android.fbjni;
+
+import app.comm.android.commservices.CommAndroidServicesClient;
+import java.io.IOException;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class NotificationsInboundKeysProvider {
+  public static String getNotifsInboundKeysForDeviceID(String deviceID)
+      throws IOException, JSONException {
+    JSONObject notifInboundKeys =
+        CommAndroidServicesClient.getInstance()
+            .getNotifsInboundKeysForDeviceSync(deviceID);
+    String curve25519 = notifInboundKeys.getString("curve25519");
+    // There are several reason to return JSON with curve25519 only:
+    //    1. We only need curve25519 to create inbound session.
+    //    2. In Session.cpp there is a convention to pass curve25519
+    //       key as JSON and then add offset length to advance
+    //       the string pointer.
+    //    3. There is a risk that stringification might not preserve
+    //       the order.
+    return new JSONObject().put("curve25519", curve25519).toString();
+  }
+}
diff --git a/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java b/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java
--- a/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java
+++ b/native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java
@@ -69,7 +69,6 @@
   private Bitmap displayableNotificationLargeIcon;
   private NotificationManager notificationManager;
   private LocalBroadcastManager localBroadcastManager;
-  private CommAndroidServicesClient servicesClient;
   private AESCryptoModuleCompat aesCryptoModule;
 
   public static final String RESCIND_KEY = "rescind";
@@ -93,7 +92,6 @@
     localBroadcastManager = LocalBroadcastManager.getInstance(this);
     displayableNotificationLargeIcon = BitmapFactory.decodeResource(
         this.getApplicationContext().getResources(), R.mipmap.ic_launcher);
-    servicesClient = new CommAndroidServicesClient();
     aesCryptoModule = new AESCryptoModuleCompat();
   }
 
@@ -337,13 +335,14 @@
     String blobHash = message.getData().get(BLOB_HASH_KEY);
     String blobHolder = message.getData().get(BLOB_HOLDER_KEY);
     try {
-      byte[] largePayload = servicesClient.getBlobSync(blobHash);
+      byte[] largePayload =
+          CommAndroidServicesClient.getInstance().getBlobSync(blobHash);
       message = aesDecryptRemoteMessage(message, largePayload);
       handleMessageInfosPersistence(message);
     } catch (Exception e) {
       Log.w("COMM", "Failure when handling large notification.", e);
     }
-    servicesClient.scheduleDeferredBlobDeletion(
+    CommAndroidServicesClient.getInstance().scheduleDeferredBlobDeletion(
         blobHash, blobHolder, this.getApplicationContext());
   }
 
diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsInboundKeysProvider.h b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsInboundKeysProvider.h
new file mode 100644
--- /dev/null
+++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsInboundKeysProvider.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <string>
+
+namespace comm {
+class NotificationsInboundKeysProvider {
+public:
+  static std::string
+  getNotifsInboundKeysForDeviceID(const std::string &deviceID);
+};
+} // namespace comm
diff --git a/native/ios/Comm.xcodeproj/project.pbxproj b/native/ios/Comm.xcodeproj/project.pbxproj
--- a/native/ios/Comm.xcodeproj/project.pbxproj
+++ b/native/ios/Comm.xcodeproj/project.pbxproj
@@ -90,6 +90,8 @@
 		CB7EF17E295C674300B17035 /* CommIOSNotifications.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB7EF17D295C5D1800B17035 /* CommIOSNotifications.mm */; };
 		CB7EF180295C674300B17035 /* CommIOSNotificationsBridgeQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB7EF17B295C580500B17035 /* CommIOSNotificationsBridgeQueue.mm */; };
 		CB90951F29534B32002F2A7F /* CommSecureStore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 71D4D7CB26C50B1000FCDBCD /* CommSecureStore.mm */; };
+		CB99DB4D2C45327C00B8055E /* NotificationsInboundKeysProvider.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB99DB4C2C45327B00B8055E /* NotificationsInboundKeysProvider.mm */; };
+		CB99DB4E2C45329500B8055E /* NotificationsInboundKeysProvider.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB99DB4C2C45327B00B8055E /* NotificationsInboundKeysProvider.mm */; };
 		CBA5F8852B6979F7005BE700 /* SQLiteConnectionManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBA5F8842B6979ED005BE700 /* SQLiteConnectionManager.cpp */; };
 		CBAAA4702B459181007599DA /* BackupOperationsExecutor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBAAA46E2B459181007599DA /* BackupOperationsExecutor.cpp */; };
 		CBAB638A2BFCCA9B003B089F /* EntryStore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBAB63882BFCB087003B089F /* EntryStore.cpp */; };
@@ -319,6 +321,8 @@
 		CB7EF17C295C580500B17035 /* CommIOSNotificationsBridgeQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CommIOSNotificationsBridgeQueue.h; path = Comm/CommIOSNotifications/CommIOSNotificationsBridgeQueue.h; sourceTree = "<group>"; };
 		CB7EF17D295C5D1800B17035 /* CommIOSNotifications.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = CommIOSNotifications.mm; path = Comm/CommIOSNotifications/CommIOSNotifications.mm; sourceTree = "<group>"; };
 		CB90951929531663002F2A7F /* CommIOSNotifications.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CommIOSNotifications.h; path = Comm/CommIOSNotifications/CommIOSNotifications.h; sourceTree = "<group>"; };
+		CB99DB4B2C45326C00B8055E /* NotificationsInboundKeysProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NotificationsInboundKeysProvider.h; path = Notifications/BackgroundDataStorage/NotificationsInboundKeysProvider.h; sourceTree = "<group>"; };
+		CB99DB4C2C45327B00B8055E /* NotificationsInboundKeysProvider.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = NotificationsInboundKeysProvider.mm; path = Comm/NotificationsInboundKeysProvider.mm; sourceTree = "<group>"; };
 		CBA5F8832B6979ED005BE700 /* SQLiteConnectionManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLiteConnectionManager.h; sourceTree = "<group>"; };
 		CBA5F8842B6979ED005BE700 /* SQLiteConnectionManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SQLiteConnectionManager.cpp; sourceTree = "<group>"; };
 		CBA784382B28AC4300E9F419 /* CommServicesAuthMetadataEmitter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommServicesAuthMetadataEmitter.h; sourceTree = "<group>"; };
@@ -432,6 +436,7 @@
 		71B8CCB626BD30EC0040C0A2 /* CommCoreImplementations */ = {
 			isa = PBXGroup;
 			children = (
+				CB99DB4C2C45327B00B8055E /* NotificationsInboundKeysProvider.mm */,
 				CBCF984C2BA499C200DBC3D9 /* CommIOSServices */,
 				CBB0DF5F2B768007008E22FF /* CommMMKV.mm */,
 				CB74AB1B2B2AFF6E00CBB494 /* CommServicesAuthMetadataEmitter.mm */,
@@ -732,6 +737,7 @@
 		CB24361529A3979500FEC4E1 /* BackgroundDataStorage */ = {
 			isa = PBXGroup;
 			children = (
+				CB99DB4B2C45326C00B8055E /* NotificationsInboundKeysProvider.h */,
 				CB24361729A39A2500FEC4E1 /* NotificationsCryptoModule.cpp */,
 				CB24361629A397AB00FEC4E1 /* NotificationsCryptoModule.h */,
 			);
@@ -1203,6 +1209,7 @@
 				8E43C32C291E5B4A009378F5 /* TerminateApp.mm in Sources */,
 				CBCF984F2BA499DA00DBC3D9 /* CommIOSServicesClient.mm in Sources */,
 				B3B02EBF2B8538980020D118 /* CommunityStore.cpp in Sources */,
+				CB99DB4D2C45327C00B8055E /* NotificationsInboundKeysProvider.mm in Sources */,
 				8BC9568529FC49B00060AE4A /* JSIRust.cpp in Sources */,
 				8EA59BD92A73DAB000EB4F53 /* rustJSI-generated.cpp in Sources */,
 				CB38B48628771CDD00171182 /* TemporaryMessageStorage.mm in Sources */,
@@ -1262,6 +1269,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				CB99DB4E2C45329500B8055E /* NotificationsInboundKeysProvider.mm in Sources */,
 				CBCF98502BA49A0500DBC3D9 /* CommIOSServicesClient.mm in Sources */,
 				CBCA09072A8E0E7D00F75B3E /* StaffUtils.cpp in Sources */,
 				CB3C0A3B2A125C8F009BD4DA /* NotificationsCryptoModule.cpp in Sources */,
diff --git a/native/ios/Comm/NotificationsInboundKeysProvider.mm b/native/ios/Comm/NotificationsInboundKeysProvider.mm
new file mode 100644
--- /dev/null
+++ b/native/ios/Comm/NotificationsInboundKeysProvider.mm
@@ -0,0 +1,31 @@
+#import "NotificationsInboundKeysProvider.h"
+#import "CommIOSServices/CommIOSServicesClient.h"
+#include <folly/dynamic.h>
+#include <folly/json.h>
+
+namespace comm {
+std::string NotificationsInboundKeysProvider::getNotifsInboundKeysForDeviceID(
+    const std::string &deviceID) {
+  NSError *error = nil;
+  NSDictionary *notifInboundKeys = [CommIOSServicesClient.sharedInstance
+      getNotifsIdentityKeysFor:[NSString stringWithCString:deviceID.c_str()
+                                                  encoding:NSUTF8StringEncoding]
+                    orSetError:&error];
+  if (error) {
+    throw std::runtime_error(
+        "Failed to fetch notifs inbound keys for device: " + deviceID +
+        ". Details: " + std::string([error.localizedDescription UTF8String]));
+  }
+
+  std::string curve25519 =
+      std::string([notifInboundKeys[@"curve25519"] UTF8String]);
+  // There are several reason to return JSON with curve25519 only:
+  //    1. We only need curve25519 to create inbound session.
+  //    2. In Session.cpp there is a convention to pass curve25519
+  //       key as JSON and then add offset length to advance
+  //       the string pointer.
+  //    3. There is a risk that stringification might not preserve
+  //       the order.
+  return folly::toJson(folly::dynamic::object("curve25519", curve25519));
+}
+} // namespace comm