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
@@ -152,4 +152,4 @@
   },
 };
 
-export { olmAPI };
+export { olmAPI, usingSharedWorker };
diff --git a/web/grpc/identity-service-client-proxy.js b/web/grpc/identity-service-client-proxy.js
new file mode 100644
--- /dev/null
+++ b/web/grpc/identity-service-client-proxy.js
@@ -0,0 +1,109 @@
+// @flow
+
+import type {
+  OneTimeKeysResultValues,
+  SignedPrekeys,
+} from 'lib/types/crypto-types.js';
+import type {
+  IdentityServiceClient,
+  IdentityServiceAuthLayer,
+  DeviceOlmOutboundKeys,
+  IdentityAuthResult,
+  UserDevicesOlmInboundKeys,
+  UserDevicesOlmOutboundKeys,
+} from 'lib/types/identity-service-types.js';
+import { getConfig } from 'lib/utils/config.js';
+
+import {
+  type CommSharedWorker,
+  getCommSharedWorker,
+} from '../shared-worker/shared-worker-provider.js';
+import { getOpaqueWasmPath } from '../shared-worker/utils/constants.js';
+import {
+  workerRequestMessageTypes,
+  workerResponseMessageTypes,
+} from '../types/worker-types.js';
+
+class IdentityServiceClientSharedProxy implements IdentityServiceClient {
+  sharedWorkerPromise: Promise<CommSharedWorker>;
+
+  constructor(authLayer: ?IdentityServiceAuthLayer) {
+    this.sharedWorkerPromise = (async () => {
+      const sharedWorker = await getCommSharedWorker();
+      await sharedWorker.schedule({
+        type: workerRequestMessageTypes.CREATE_IDENTITY_SERVICE_CLIENT,
+        opaqueWasmPath: getOpaqueWasmPath(),
+        platformDetails: getConfig().platformDetails,
+        authLayer,
+      });
+
+      return sharedWorker;
+    })();
+  }
+
+  proxyToWorker<T>(
+    method: $Keys<IdentityServiceClient>,
+  ): (...args: $ReadOnlyArray<mixed>) => Promise<T> {
+    return async (...args: $ReadOnlyArray<mixed>) => {
+      const sharedWorker = await this.sharedWorkerPromise;
+      const result = await sharedWorker.schedule({
+        type: workerRequestMessageTypes.CALL_IDENTITY_CLIENT_METHOD,
+        method,
+        args,
+      });
+
+      if (!result) {
+        throw new Error(`Worker identity call didn't return expected message`);
+      } else if (
+        result.type !== workerResponseMessageTypes.CALL_IDENTITY_CLIENT_METHOD
+      ) {
+        throw new Error(
+          `Worker identity call didn't return expected message. Instead got: ${JSON.stringify(
+            result,
+          )}`,
+        );
+      }
+
+      // Worker should return a message with the corresponding return type
+      return (result.result: any);
+    };
+  }
+
+  deleteUser: () => Promise<void> = this.proxyToWorker('deleteUser');
+
+  getKeyserverKeys: (keyserverID: string) => Promise<DeviceOlmOutboundKeys> =
+    this.proxyToWorker('getKeyserverKeys');
+
+  getOutboundKeysForUser: (
+    userID: string,
+  ) => Promise<UserDevicesOlmOutboundKeys[]> = this.proxyToWorker(
+    'getOutboundKeysForUser',
+  );
+
+  getInboundKeysForUser: (
+    userID: string,
+  ) => Promise<UserDevicesOlmInboundKeys> = this.proxyToWorker(
+    'getInboundKeysForUser',
+  );
+
+  uploadOneTimeKeys: (oneTimeKeys: OneTimeKeysResultValues) => Promise<void> =
+    this.proxyToWorker('uploadOneTimeKeys');
+
+  logInPasswordUser: (
+    username: string,
+    password: string,
+  ) => Promise<IdentityAuthResult> = this.proxyToWorker('logInPasswordUser');
+
+  logInWalletUser: (
+    walletAddress: string,
+    siweMessage: string,
+    siweSignature: string,
+  ) => Promise<IdentityAuthResult> = this.proxyToWorker('logInWalletUser');
+
+  generateNonce: () => Promise<string> = this.proxyToWorker('generateNonce');
+
+  publishWebPrekeys: (prekeys: SignedPrekeys) => Promise<void> =
+    this.proxyToWorker('publishWebPrekeys');
+}
+
+export { IdentityServiceClientSharedProxy };
diff --git a/web/grpc/identity-service-context-provider.react.js b/web/grpc/identity-service-context-provider.react.js
--- a/web/grpc/identity-service-context-provider.react.js
+++ b/web/grpc/identity-service-context-provider.react.js
@@ -8,8 +8,10 @@
 } from 'lib/shared/identity-client-context.js';
 import { getConfig } from 'lib/utils/config.js';
 
+import { IdentityServiceClientSharedProxy } from './identity-service-client-proxy.js';
 import { IdentityServiceClientWrapper } from './identity-service-client-wrapper.js';
 import { useGetDeviceKeyUpload } from '../account/account-hooks.js';
+import { usingSharedWorker } from '../crypto/olm-api.js';
 import { useSelector } from '../redux/redux-utils.js';
 
 type Props = {
@@ -34,12 +36,16 @@
         commServicesAccessToken: accessToken,
       };
     }
-    return new IdentityServiceClientWrapper(
-      getConfig().platformDetails,
-      null,
-      authLayer,
-      getDeviceKeyUpload,
-    );
+    if (usingSharedWorker) {
+      return new IdentityServiceClientSharedProxy(authLayer);
+    } else {
+      return new IdentityServiceClientWrapper(
+        getConfig().platformDetails,
+        null,
+        authLayer,
+        getDeviceKeyUpload,
+      );
+    }
   }, [accessToken, deviceID, getDeviceKeyUpload, userID]);
 
   const getAuthMetadata = React.useCallback<() => Promise<AuthMetadata>>(
diff --git a/web/shared-worker/shared-worker-provider.js b/web/shared-worker/shared-worker-provider.js
--- a/web/shared-worker/shared-worker-provider.js
+++ b/web/shared-worker/shared-worker-provider.js
@@ -166,4 +166,4 @@
   return newModule;
 }
 
-export { getCommSharedWorker };
+export { CommSharedWorker, getCommSharedWorker };
diff --git a/web/shared-worker/worker/identity-client.js b/web/shared-worker/worker/identity-client.js
--- a/web/shared-worker/worker/identity-client.js
+++ b/web/shared-worker/worker/identity-client.js
@@ -2,10 +2,11 @@
 
 import { getDeviceKeyUpload } from './worker-crypto.js';
 import { IdentityServiceClientWrapper } from '../../grpc/identity-service-client-wrapper.js';
-import { workerRequestMessageTypes } from '../../types/worker-types.js';
-import type {
-  WorkerResponseMessage,
-  WorkerRequestMessage,
+import {
+  type WorkerResponseMessage,
+  type WorkerRequestMessage,
+  workerRequestMessageTypes,
+  workerResponseMessageTypes,
 } from '../../types/worker-types.js';
 import type { EmscriptenModule } from '../types/module.js';
 import type { SQLiteQueryExecutor } from '../types/sqlite-query-executor.js';
@@ -26,7 +27,31 @@
       message.authLayer,
       async () => getDeviceKeyUpload(),
     );
+    return undefined;
   }
+
+  if (!identityClient) {
+    throw new Error('Identity client not created');
+  }
+
+  if (message.type === workerRequestMessageTypes.CALL_IDENTITY_CLIENT_METHOD) {
+    // Flow doesn't allow us to access methods like this (it needs an index
+    // signature declaration in the object type)
+    // $FlowFixMe
+    const method = identityClient[message.method];
+    if (typeof method !== 'function') {
+      throw new Error(
+        `Couldn't find identity client method with name '${message.method}'`,
+      );
+    }
+    const result = await method(...message.args);
+    return {
+      type: workerResponseMessageTypes.CALL_IDENTITY_CLIENT_METHOD,
+      result,
+    };
+  }
+
+  return undefined;
 }
 
 function getIdentityClient(): ?IdentityServiceClientWrapper {
diff --git a/web/types/worker-types.js b/web/types/worker-types.js
--- a/web/types/worker-types.js
+++ b/web/types/worker-types.js
@@ -3,7 +3,10 @@
 import type { AuthMetadata } from 'lib/shared/identity-client-context.js';
 import type { CryptoStore } from 'lib/types/crypto-types.js';
 import type { PlatformDetails } from 'lib/types/device-types.js';
-import type { IdentityServiceAuthLayer } from 'lib/types/identity-service-types.js';
+import type {
+  IdentityServiceClient,
+  IdentityServiceAuthLayer,
+} from 'lib/types/identity-service-types.js';
 import type {
   ClientDBStore,
   ClientDBStoreOperations,
@@ -25,6 +28,7 @@
   BACKUP_RESTORE: 11,
   INITIALIZE_CRYPTO_ACCOUNT: 12,
   CREATE_IDENTITY_SERVICE_CLIENT: 13,
+  CALL_IDENTITY_CLIENT_METHOD: 14,
 });
 
 export const workerWriteRequests: $ReadOnlyArray<number> = [
@@ -42,6 +46,7 @@
 
 export const workerIdentityClientRequests: $ReadOnlyArray<number> = [
   workerRequestMessageTypes.CREATE_IDENTITY_SERVICE_CLIENT,
+  workerRequestMessageTypes.CALL_IDENTITY_CLIENT_METHOD,
 ];
 
 export type PingWorkerRequestMessage = {
@@ -120,6 +125,12 @@
   +authLayer: ?IdentityServiceAuthLayer,
 };
 
+export type CallIdentityClientMethodRequestMessage = {
+  +type: 14,
+  +method: $Keys<IdentityServiceClient>,
+  +args: $ReadOnlyArray<mixed>,
+};
+
 export type WorkerRequestMessage =
   | PingWorkerRequestMessage
   | InitWorkerRequestMessage
@@ -134,7 +145,8 @@
   | ClearSensitiveDataRequestMessage
   | BackupRestoreRequestMessage
   | InitializeCryptoAccountRequestMessage
-  | CreateIdentityServiceClientRequestMessage;
+  | CreateIdentityServiceClientRequestMessage
+  | CallIdentityClientMethodRequestMessage;
 
 export type WorkerRequestProxyMessage = {
   +id: number,
@@ -147,6 +159,7 @@
   CLIENT_STORE: 1,
   GET_CURRENT_USER_ID: 2,
   GET_PERSIST_STORAGE_ITEM: 3,
+  CALL_IDENTITY_CLIENT_METHOD: 4,
 });
 
 export type PongWorkerResponseMessage = {
@@ -169,11 +182,17 @@
   +item: string,
 };
 
+export type CallIdentityClientMethodResponseMessage = {
+  +type: 4,
+  +result: mixed,
+};
+
 export type WorkerResponseMessage =
   | PongWorkerResponseMessage
   | ClientStoreResponseMessage
   | GetCurrentUserIDResponseMessage
-  | GetPersistStorageItemResponseMessage;
+  | GetPersistStorageItemResponseMessage
+  | CallIdentityClientMethodResponseMessage;
 
 export type WorkerResponseProxyMessage = {
   +id?: number,