diff --git a/lib/types/crypto-types.js b/lib/types/crypto-types.js
--- a/lib/types/crypto-types.js
+++ b/lib/types/crypto-types.js
@@ -149,6 +149,12 @@
     contentIdentityKeys: OLMIdentityKeys,
     contentInitializationInfo: OlmSessionInitializationInfo,
   ) => Promise<string>,
+  +notificationsSessionCreator: (
+    cookie: ?string,
+    notificationsIdentityKeys: OLMIdentityKeys,
+    notificationsInitializationInfo: OlmSessionInitializationInfo,
+    keyserverID: string,
+  ) => Promise<string>,
   +getOneTimeKeys: (numberOfKeys: number) => Promise<OneTimeKeysResultValues>,
   +validateAndUploadPrekeys: (authMetadata: AuthMetadata) => Promise<void>,
 };
diff --git a/lib/utils/__mocks__/config.js b/lib/utils/__mocks__/config.js
--- a/lib/utils/__mocks__/config.js
+++ b/lib/utils/__mocks__/config.js
@@ -19,6 +19,7 @@
     decrypt: jest.fn(),
     contentInboundSessionCreator: jest.fn(),
     contentOutboundSessionCreator: jest.fn(),
+    notificationsSessionCreator: jest.fn(),
     getOneTimeKeys: jest.fn(),
     validateAndUploadPrekeys: jest.fn(),
   },
diff --git a/native/crypto/olm-api.js b/native/crypto/olm-api.js
--- a/native/crypto/olm-api.js
+++ b/native/crypto/olm-api.js
@@ -50,6 +50,22 @@
       contentIdentityKeys.ed25519,
     );
   },
+  notificationsSessionCreator(
+    cookie: ?string,
+    notificationsIdentityKeys: OLMIdentityKeys,
+    notificationsInitializationInfo: OlmSessionInitializationInfo,
+    keyserverID: string,
+  ): Promise<string> {
+    const { prekey, prekeySignature, oneTimeKey } =
+      notificationsInitializationInfo;
+    return commCoreModule.initializeNotificationsSession(
+      JSON.stringify(notificationsIdentityKeys),
+      prekey,
+      prekeySignature,
+      oneTimeKey,
+      keyserverID,
+    );
+  },
   async getOneTimeKeys(numberOfKeys: number): Promise<OneTimeKeysResultValues> {
     const { contentOneTimeKeys, notificationsOneTimeKeys } =
       await commCoreModule.getOneTimeKeys(numberOfKeys);
diff --git a/web/crypto/olm-api.js b/web/crypto/olm-api.js
--- a/web/crypto/olm-api.js
+++ b/web/crypto/olm-api.js
@@ -56,6 +56,7 @@
   decrypt: proxyToWorker('decrypt'),
   contentInboundSessionCreator: proxyToWorker('contentInboundSessionCreator'),
   contentOutboundSessionCreator: proxyToWorker('contentOutboundSessionCreator'),
+  notificationsSessionCreator: proxyToWorker('notificationsSessionCreator'),
   getOneTimeKeys: proxyToWorker('getOneTimeKeys'),
   validateAndUploadPrekeys: proxyToWorker('validateAndUploadPrekeys'),
 };
diff --git a/web/shared-worker/worker/worker-crypto.js b/web/shared-worker/worker/worker-crypto.js
--- a/web/shared-worker/worker/worker-crypto.js
+++ b/web/shared-worker/worker/worker-crypto.js
@@ -1,9 +1,14 @@
 // @flow
 
 import olm from '@commapp/olm';
+import localforage from 'localforage';
 import uuid from 'uuid';
 
 import { initialEncryptedMessageContent } from 'lib/shared/crypto-utils.js';
+import {
+  hasMinCodeVersion,
+  NEXT_CODE_VERSION,
+} from 'lib/shared/version-utils.js';
 import {
   olmEncryptedMessageTypes,
   type OLMIdentityKeys,
@@ -14,6 +19,7 @@
   type OlmAPI,
   type OneTimeKeysResultValues,
   type ClientPublicKeys,
+  type NotificationsOlmDataType,
 } from 'lib/types/crypto-types.js';
 import type {
   IdentityNewDeviceKeyUpload,
@@ -32,7 +38,20 @@
 
 import { getIdentityClient } from './identity-client.js';
 import { getProcessingStoreOpsExceptionMessage } from './process-operations.js';
-import { getDBModule, getSQLiteQueryExecutor } from './worker-database.js';
+import {
+  getDBModule,
+  getSQLiteQueryExecutor,
+  getPlatformDetails,
+} from './worker-database.js';
+import {
+  encryptData,
+  exportKeyToJWK,
+  generateCryptoKey,
+} from '../../crypto/aes-gcm-crypto-utils.js';
+import {
+  getOlmDataContentKeyForCookie,
+  getOlmEncryptionKeyDBLabelForCookie,
+} from '../../push-notif/notif-crypto-utils.js';
 import {
   type WorkerRequestMessage,
   type WorkerResponseMessage,
@@ -40,6 +59,7 @@
   workerResponseMessageTypes,
 } from '../../types/worker-types.js';
 import type { OlmPersistSession } from '../types/sqlite-query-executor.js';
+import { isDesktopSafari } from '../utils/db-utils.js';
 
 type WorkerCryptoStore = {
   +contentAccountPickleKey: string,
@@ -435,6 +455,95 @@
 
     return initialContentEncryptedMessage;
   },
+  async notificationsSessionCreator(
+    cookie: ?string,
+    notificationsIdentityKeys: OLMIdentityKeys,
+    notificationsInitializationInfo: OlmSessionInitializationInfo,
+    keyserverID: string,
+  ): Promise<string> {
+    const platformDetails = getPlatformDetails();
+    if (!platformDetails) {
+      throw new Error('Worker not initialized');
+    }
+
+    if (!cryptoStore) {
+      throw new Error('Crypto account not initialized');
+    }
+
+    const { notificationAccountPickleKey, notificationAccount } = cryptoStore;
+    const encryptionKey = await generateCryptoKey({
+      extractable: isDesktopSafari,
+    });
+
+    const notificationsPrekey = notificationsInitializationInfo.prekey;
+    const session = new olm.Session();
+    session.create_outbound(
+      notificationAccount,
+      notificationsIdentityKeys.curve25519,
+      notificationsIdentityKeys.ed25519,
+      notificationsPrekey,
+      notificationsInitializationInfo.prekeySignature,
+      notificationsInitializationInfo.oneTimeKey,
+    );
+    const { body: initialNotificationsEncryptedMessage } = session.encrypt(
+      JSON.stringify(initialEncryptedMessageContent),
+    );
+
+    const mainSession = session.pickle(notificationAccountPickleKey);
+    const notificationsOlmData: NotificationsOlmDataType = {
+      mainSession,
+      pendingSessionUpdate: mainSession,
+      updateCreationTimestamp: Date.now(),
+      picklingKey: notificationAccountPickleKey,
+    };
+    const encryptedOlmData = await encryptData(
+      new TextEncoder().encode(JSON.stringify(notificationsOlmData)),
+      encryptionKey,
+    );
+
+    let notifsOlmDataContentKey;
+    let notifsOlmDataEncryptionKeyDBLabel;
+
+    if (
+      hasMinCodeVersion(platformDetails, { majorDesktop: NEXT_CODE_VERSION })
+    ) {
+      notifsOlmDataEncryptionKeyDBLabel = getOlmEncryptionKeyDBLabelForCookie(
+        cookie,
+        keyserverID,
+      );
+      notifsOlmDataContentKey = getOlmDataContentKeyForCookie(
+        cookie,
+        keyserverID,
+      );
+    } else {
+      notifsOlmDataEncryptionKeyDBLabel =
+        getOlmEncryptionKeyDBLabelForCookie(cookie);
+      notifsOlmDataContentKey = getOlmDataContentKeyForCookie(cookie);
+    }
+
+    const persistEncryptionKeyPromise = (async () => {
+      let cryptoKeyPersistentForm;
+      if (isDesktopSafari) {
+        // Safari doesn't support structured clone algorithm in service
+        // worker context so we have to store CryptoKey as JSON
+        cryptoKeyPersistentForm = await exportKeyToJWK(encryptionKey);
+      } else {
+        cryptoKeyPersistentForm = encryptionKey;
+      }
+
+      await localforage.setItem(
+        notifsOlmDataEncryptionKeyDBLabel,
+        cryptoKeyPersistentForm,
+      );
+    })();
+
+    await Promise.all([
+      localforage.setItem(notifsOlmDataContentKey, encryptedOlmData),
+      persistEncryptionKeyPromise,
+    ]);
+
+    return initialNotificationsEncryptedMessage;
+  },
   async getOneTimeKeys(numberOfKeys: number): Promise<OneTimeKeysResultValues> {
     if (!cryptoStore) {
       throw new Error('Crypto account not initialized');