diff --git a/lib/facts/identity-service.js b/lib/facts/identity-service.js
--- a/lib/facts/identity-service.js
+++ b/lib/facts/identity-service.js
@@ -2,10 +2,34 @@
 
 import { isDev } from '../utils/dev-utils.js';
 
-const config: { defaultURL: string } = {
+type IdentityServicePath = '/device_inbound_keys?device_id=';
+
+type IdentityServiceEndpoint = {
+  +path: IdentityServicePath,
+  +method: 'PUT' | 'GET' | 'POST' | 'DELETE',
+};
+
+const httpEndpoints = Object.freeze({
+  GET_INBOUND_KEYS: {
+    path: '/device_inbound_keys?device_id=',
+    method: 'GET',
+  },
+});
+
+type IdentityServiceConfig = {
+  +defaultURL: string,
+  +defaultHttpURL: string,
+  +httpEndpoints: { +[endpoint: string]: IdentityServiceEndpoint },
+};
+
+const config: IdentityServiceConfig = {
   defaultURL: isDev
     ? 'https://identity.staging.commtechnologies.org:50054'
     : 'https://identity.commtechnologies.org:50054',
+  defaultHttpURL: isDev
+    ? 'https://identity.staging.commtechnologies.org:51004'
+    : 'https://identity.commtechnologies.org:51004',
+  httpEndpoints,
 };
 
 export default config;
diff --git a/lib/utils/identity-service.js b/lib/utils/identity-service.js
new file mode 100644
--- /dev/null
+++ b/lib/utils/identity-service.js
@@ -0,0 +1,48 @@
+// @flow
+
+import type { TInterface } from 'tcomb';
+import t from 'tcomb';
+
+import identityServiceConfig from '../facts/identity-service.js';
+import { tShape } from '../utils/validation-utils.js';
+
+export type InboundKeysForDeviceResponse = {
+  identityKeyInfo: {
+    keyPayload: string,
+    keyPayloadSignature: string,
+  },
+  contentPrekey: {
+    prekey: string,
+    prekeySignature: string,
+  },
+  notifPrekey: {
+    prekey: string,
+    prekeySignature: string,
+  },
+};
+
+export const inboundKeysForDeviceResponseValidator: TInterface<InboundKeysForDeviceResponse> =
+  tShape<InboundKeysForDeviceResponse>({
+    identityKeyInfo: tShape({
+      keyPayload: t.String,
+      keyPayloadSignature: t.String,
+    }),
+    contentPrekey: tShape({
+      prekey: t.String,
+      prekeySignature: t.String,
+    }),
+    notifPrekey: tShape({
+      prekey: t.String,
+      prekeySignature: t.String,
+    }),
+  });
+
+function getInboundKeysForDeviceURL(deviceID: string): string {
+  const urlSafeDeviceID = deviceID.replaceAll('+', '-').replaceAll('/', '_');
+  const endpointBasePath =
+    identityServiceConfig.httpEndpoints.GET_INBOUND_KEYS.path;
+  const path = `${endpointBasePath}${urlSafeDeviceID}`;
+  return `${identityServiceConfig.defaultHttpURL}${path}`;
+}
+
+export { getInboundKeysForDeviceURL };
diff --git a/web/push-notif/push-notifs-handler.js b/web/push-notif/push-notifs-handler.js
--- a/web/push-notif/push-notifs-handler.js
+++ b/web/push-notif/push-notifs-handler.js
@@ -1,5 +1,6 @@
 // @flow
 
+import invariant from 'invariant';
 import * as React from 'react';
 
 import { recordAlertActionType } from 'lib/actions/alert-actions.js';
@@ -9,6 +10,7 @@
 } from 'lib/actions/device-actions.js';
 import { useModalContext } from 'lib/components/modal-provider.react.js';
 import { isLoggedIn } from 'lib/selectors/user-selectors.js';
+import { IdentityClientContext } from 'lib/shared/identity-client-context.js';
 import { hasMinCodeVersion } from 'lib/shared/version-utils.js';
 import {
   alertTypes,
@@ -141,19 +143,29 @@
   const callSetDeviceToken = useSetDeviceTokenFanout();
   const staffCanSee = useStaffCanSee();
 
+  const identityContext = React.useContext(IdentityClientContext);
+  invariant(identityContext, 'Identity context should be set');
+  const { getAuthMetadata } = identityContext;
+
   return React.useCallback(async () => {
     if (!publicKey) {
       return;
     }
 
     const workerRegistration = await navigator.serviceWorker?.ready;
-    if (!workerRegistration || !workerRegistration.pushManager) {
+    const authMetadata = await getAuthMetadata();
+    if (
+      !workerRegistration ||
+      !workerRegistration.pushManager ||
+      !authMetadata
+    ) {
       return;
     }
 
     workerRegistration.active?.postMessage({
       olmWasmPath: getOlmWasmPath(),
       staffCanSee,
+      authMetadata,
     });
 
     const subscription = await workerRegistration.pushManager.subscribe({
@@ -168,7 +180,13 @@
       undefined,
       { type: 'device_token', deviceToken: token },
     );
-  }, [callSetDeviceToken, dispatchActionPromise, publicKey, staffCanSee]);
+  }, [
+    callSetDeviceToken,
+    dispatchActionPromise,
+    publicKey,
+    staffCanSee,
+    getAuthMetadata,
+  ]);
 }
 
 function PushNotificationsHandler(): React.Node {
diff --git a/web/push-notif/service-worker.js b/web/push-notif/service-worker.js
--- a/web/push-notif/service-worker.js
+++ b/web/push-notif/service-worker.js
@@ -2,6 +2,7 @@
 
 import localforage from 'localforage';
 
+import type { AuthMetadata } from 'lib/shared/identity-client-context.js';
 import type {
   PlainTextWebNotification,
   WebNotification,
@@ -15,6 +16,7 @@
   type WebNotifsServiceUtilsData,
   type WebNotifDecryptionError,
 } from './notif-crypto-utils.js';
+import { persistAuthMetadata } from './services-client.js';
 import { authoritativeKeyserverID } from '../authoritative-keyserver.js';
 import { localforageConfig } from '../shared-worker/utils/constants.js';
 
@@ -26,7 +28,11 @@
 }
 
 declare class CommAppMessage extends ExtendableEvent {
-  +data: { +olmWasmPath?: string, +staffCanSee?: boolean };
+  +data: {
+    +olmWasmPath?: string,
+    +staffCanSee?: boolean,
+    +authMetadata?: AuthMetadata,
+  };
 }
 
 declare var clients: Clients;
@@ -66,19 +72,24 @@
   localforage.config(localforageConfig);
   event.waitUntil(
     (async () => {
-      if (!event.data.olmWasmPath || event.data.staffCanSee === undefined) {
+      const { olmWasmPath, staffCanSee, authMetadata } = event.data;
+
+      if (!olmWasmPath || staffCanSee === undefined || !authMetadata) {
         return;
       }
 
       const webNotifsServiceUtils: WebNotifsServiceUtilsData = {
-        olmWasmPath: event.data.olmWasmPath,
-        staffCanSee: event.data.staffCanSee,
+        olmWasmPath: olmWasmPath,
+        staffCanSee: staffCanSee,
       };
 
-      await localforage.setItem(
-        WEB_NOTIFS_SERVICE_UTILS_KEY,
-        webNotifsServiceUtils,
-      );
+      await Promise.all([
+        localforage.setItem(
+          WEB_NOTIFS_SERVICE_UTILS_KEY,
+          webNotifsServiceUtils,
+        ),
+        persistAuthMetadata(authMetadata),
+      ]);
 
       await migrateLegacyOlmNotificationsSessions();
     })(),
diff --git a/web/push-notif/services-client.js b/web/push-notif/services-client.js
new file mode 100644
--- /dev/null
+++ b/web/push-notif/services-client.js
@@ -0,0 +1,120 @@
+// @flow
+
+import localforage from 'localforage';
+
+import identityServiceConfig from 'lib/facts/identity-service.js';
+import type { AuthMetadata } from 'lib/shared/identity-client-context.js';
+import {
+  identityKeysBlobValidator,
+  type OLMIdentityKeys,
+} from 'lib/types/crypto-types.js';
+import { getMessageForException } from 'lib/utils/errors.js';
+import {
+  getInboundKeysForDeviceURL,
+  inboundKeysForDeviceResponseValidator,
+} from 'lib/utils/identity-service.js';
+import { createHTTPAuthorizationHeader } from 'lib/utils/services-utils.js';
+import { assertWithValidator } from 'lib/utils/validation-utils.js';
+
+import {
+  persistEncryptionKey,
+  retrieveEncryptionKey,
+} from './notif-crypto-utils.js';
+import {
+  type EncryptedData,
+  decryptData,
+  encryptData,
+  generateCryptoKey,
+} from '../crypto/aes-gcm-crypto-utils.js';
+import { isDesktopSafari } from '../shared-worker/utils/db-utils.js';
+
+export const WEB_NOTIFS_SERVICE_CSAT_ENCRYPTION_KEY = 'notifsCSATEncryptionKey';
+export const WEB_NOTIFS_SERVICE_CSAT = 'notifsCSAT';
+
+async function persistAuthMetadata(authMetadata: AuthMetadata): Promise<void> {
+  const encryptionKey = await generateCryptoKey({
+    extractable: isDesktopSafari,
+  });
+
+  const encryptedAuthMetadata = await encryptData(
+    new TextEncoder().encode(JSON.stringify(authMetadata)),
+    encryptionKey,
+  );
+
+  await Promise.all([
+    localforage.setItem(WEB_NOTIFS_SERVICE_CSAT, encryptedAuthMetadata),
+    persistEncryptionKey(WEB_NOTIFS_SERVICE_CSAT_ENCRYPTION_KEY, encryptionKey),
+  ]);
+}
+
+async function fetchAuthMetadata(): Promise<AuthMetadata> {
+  const [encryptionKey, encryptedAuthMetadata] = await Promise.all([
+    retrieveEncryptionKey(WEB_NOTIFS_SERVICE_CSAT_ENCRYPTION_KEY),
+    localforage.getItem<EncryptedData>(WEB_NOTIFS_SERVICE_CSAT),
+  ]);
+
+  if (!encryptionKey || !encryptedAuthMetadata) {
+    throw new Error('CSAT unavailable in push notifs service worker');
+  }
+
+  const authMetadata: AuthMetadata = JSON.parse(
+    new TextDecoder().decode(
+      await decryptData(encryptedAuthMetadata, encryptionKey),
+    ),
+  );
+
+  return authMetadata;
+}
+
+async function getNotifsInboundKeysForDeviceID(
+  deviceID: string,
+  authMetadata: AuthMetadata,
+): Promise<OLMIdentityKeys | { error: string }> {
+  const authorization = createHTTPAuthorizationHeader(authMetadata);
+  const headers = {
+    Authorization: authorization,
+    Accept: 'application/json',
+  };
+  try {
+    const getInboundKeysResponse = await fetch(
+      getInboundKeysForDeviceURL(deviceID),
+      {
+        method: identityServiceConfig.httpEndpoints.GET_INBOUND_KEYS.method,
+        headers,
+      },
+    );
+
+    if (!getInboundKeysResponse.ok) {
+      const { statusText, status } = getInboundKeysResponse;
+      return {
+        error:
+          `Failed to fetch inbound keys for ${deviceID} with code: ${status}. ` +
+          `Details: ${statusText}`,
+      };
+    }
+
+    const inboundKeysForDeviceBlob = await getInboundKeysResponse.json();
+    const inboundKeysForDevice = assertWithValidator(
+      inboundKeysForDeviceBlob,
+      inboundKeysForDeviceResponseValidator,
+    );
+    const identityKeysBlob = inboundKeysForDevice.identityKeyInfo.keyPayload;
+    const identityKeys = assertWithValidator(
+      JSON.parse(identityKeysBlob),
+      identityKeysBlobValidator,
+    );
+    return identityKeys.notificationIdentityPublicKeys;
+  } catch (e) {
+    return {
+      error: `Failed to fetch inbound keys for ${deviceID}. Details: ${
+        getMessageForException(e) ?? ''
+      }`,
+    };
+  }
+}
+
+export {
+  persistAuthMetadata,
+  fetchAuthMetadata,
+  getNotifsInboundKeysForDeviceID,
+};