diff --git a/keyserver/src/shared/state-sync/current-user-state-sync-spec.js b/keyserver/src/shared/state-sync/current-user-state-sync-spec.js
--- a/keyserver/src/shared/state-sync/current-user-state-sync-spec.js
+++ b/keyserver/src/shared/state-sync/current-user-state-sync-spec.js
@@ -2,10 +2,13 @@
 
 import { currentUserStateSyncSpec as libSpec } from 'lib/shared/state-sync/current-user-state-sync-spec.js';
 import type { CurrentUserInfo } from 'lib/types/user-types.js';
+import { currentUserInfoValidator } from 'lib/types/user-types.js';
+import { hash } from 'lib/utils/objects.js';
 
 import type { ServerStateSyncSpec } from './state-sync-spec.js';
 import { fetchCurrentUserInfo } from '../../fetchers/user-fetchers.js';
 import type { Viewer } from '../../session/viewer.js';
+import { validateOutput } from '../../utils/validation-utils.js';
 
 export const currentUserStateSyncSpec: ServerStateSyncSpec<
   CurrentUserInfo,
@@ -13,11 +16,23 @@
   CurrentUserInfo,
   void,
 > = Object.freeze({
-  fetch(viewer: Viewer) {
-    return fetchCurrentUserInfo(viewer);
-  },
+  fetch,
   fetchFullSocketSyncPayload(viewer: Viewer) {
     return fetchCurrentUserInfo(viewer);
   },
+  async fetchServerInfosHash(viewer: Viewer) {
+    const info = await fetch(viewer);
+    return getHash(info);
+  },
+  getServerInfosHash: getHash,
+  getServerInfoHash: getHash,
   ...libSpec,
 });
+
+function fetch(viewer: Viewer) {
+  return fetchCurrentUserInfo(viewer);
+}
+
+function getHash(currentUserInfo: CurrentUserInfo) {
+  return hash(validateOutput(null, currentUserInfoValidator, currentUserInfo));
+}
diff --git a/keyserver/src/shared/state-sync/entries-state-sync-spec.js b/keyserver/src/shared/state-sync/entries-state-sync-spec.js
--- a/keyserver/src/shared/state-sync/entries-state-sync-spec.js
+++ b/keyserver/src/shared/state-sync/entries-state-sync-spec.js
@@ -2,12 +2,14 @@
 
 import { serverEntryInfosObject } from 'lib/shared/entry-utils.js';
 import { entriesStateSyncSpec as libSpec } from 'lib/shared/state-sync/entries-state-sync-spec.js';
-import type {
-  CalendarQuery,
-  RawEntryInfo,
-  RawEntryInfos,
+import {
+  type CalendarQuery,
+  type RawEntryInfo,
+  type RawEntryInfos,
+  rawEntryInfoValidator,
 } from 'lib/types/entry-types.js';
 import type { ClientEntryInconsistencyReportCreationRequest } from 'lib/types/report-types.js';
+import { hash, combineUnorderedHashes, values } from 'lib/utils/objects.js';
 
 import type { ServerStateSyncSpec } from './state-sync-spec.js';
 import {
@@ -15,6 +17,7 @@
   fetchEntryInfosByID,
 } from '../../fetchers/entry-fetchers.js';
 import type { Viewer } from '../../session/viewer.js';
+import { validateOutput } from '../../utils/validation-utils.js';
 
 export const entriesStateSyncSpec: ServerStateSyncSpec<
   RawEntryInfos,
@@ -22,14 +25,7 @@
   RawEntryInfo,
   $ReadOnlyArray<ClientEntryInconsistencyReportCreationRequest>,
 > = Object.freeze({
-  async fetch(viewer: Viewer, ids?: $ReadOnlySet<string>) {
-    if (ids) {
-      return fetchEntryInfosByID(viewer, ids);
-    }
-    const query = [viewer.calendarQuery];
-    const entriesResult = await fetchEntryInfos(viewer, query);
-    return serverEntryInfosObject(entriesResult.rawEntryInfos);
-  },
+  fetch,
   async fetchFullSocketSyncPayload(
     viewer: Viewer,
     query: $ReadOnlyArray<CalendarQuery>,
@@ -37,5 +33,28 @@
     const result = await fetchEntryInfos(viewer, query);
     return result.rawEntryInfos;
   },
+  async fetchServerInfosHash(viewer: Viewer, ids?: $ReadOnlySet<string>) {
+    const info = await fetch(viewer, ids);
+    return getServerInfosHash(info);
+  },
+  getServerInfosHash,
+  getServerInfoHash,
   ...libSpec,
 });
+
+async function fetch(viewer: Viewer, ids?: $ReadOnlySet<string>) {
+  if (ids) {
+    return fetchEntryInfosByID(viewer, ids);
+  }
+  const query = [viewer.calendarQuery];
+  const entriesResult = await fetchEntryInfos(viewer, query);
+  return serverEntryInfosObject(entriesResult.rawEntryInfos);
+}
+
+function getServerInfosHash(infos: RawEntryInfos) {
+  return combineUnorderedHashes(values(infos).map(getServerInfoHash));
+}
+
+function getServerInfoHash(info: RawEntryInfo) {
+  return hash(validateOutput(null, rawEntryInfoValidator, info));
+}
diff --git a/keyserver/src/shared/state-sync/state-sync-spec.js b/keyserver/src/shared/state-sync/state-sync-spec.js
--- a/keyserver/src/shared/state-sync/state-sync-spec.js
+++ b/keyserver/src/shared/state-sync/state-sync-spec.js
@@ -16,5 +16,11 @@
     viewer: Viewer,
     calendarQuery: $ReadOnlyArray<CalendarQuery>,
   ) => Promise<FullSocketSyncPayload>,
+  +fetchServerInfosHash: (
+    viewer: Viewer,
+    ids?: $ReadOnlySet<string>,
+  ) => Promise<number>,
+  +getServerInfosHash: (infos: Infos) => number,
+  +getServerInfoHash: (info: Info) => number,
   ...StateSyncSpec<Infos, Info, Inconsistencies>,
 };
diff --git a/keyserver/src/shared/state-sync/threads-state-sync-spec.js b/keyserver/src/shared/state-sync/threads-state-sync-spec.js
--- a/keyserver/src/shared/state-sync/threads-state-sync-spec.js
+++ b/keyserver/src/shared/state-sync/threads-state-sync-spec.js
@@ -5,11 +5,14 @@
 import {
   type RawThreadInfos,
   type RawThreadInfo,
+  rawThreadInfoValidator,
 } from 'lib/types/thread-types.js';
+import { hash, combineUnorderedHashes, values } from 'lib/utils/objects.js';
 
 import type { ServerStateSyncSpec } from './state-sync-spec.js';
 import { fetchThreadInfos } from '../../fetchers/thread-fetchers.js';
 import type { Viewer } from '../../session/viewer.js';
+import { validateOutput } from '../../utils/validation-utils.js';
 
 export const threadsStateSyncSpec: ServerStateSyncSpec<
   RawThreadInfos,
@@ -17,14 +20,30 @@
   RawThreadInfo,
   $ReadOnlyArray<ClientThreadInconsistencyReportCreationRequest>,
 > = Object.freeze({
-  async fetch(viewer: Viewer, ids?: $ReadOnlySet<string>) {
-    const filter = ids ? { threadIDs: ids } : undefined;
-    const result = await fetchThreadInfos(viewer, filter);
-    return result.threadInfos;
-  },
+  fetch,
   async fetchFullSocketSyncPayload(viewer: Viewer) {
     const result = await fetchThreadInfos(viewer);
     return result.threadInfos;
   },
+  async fetchServerInfosHash(viewer: Viewer, ids?: $ReadOnlySet<string>) {
+    const infos = await fetch(viewer, ids);
+    return getServerInfosHash(infos);
+  },
+  getServerInfosHash,
+  getServerInfoHash,
   ...libSpec,
 });
+
+async function fetch(viewer: Viewer, ids?: $ReadOnlySet<string>) {
+  const filter = ids ? { threadIDs: ids } : undefined;
+  const result = await fetchThreadInfos(viewer, filter);
+  return result.threadInfos;
+}
+
+function getServerInfosHash(infos: RawThreadInfos) {
+  return combineUnorderedHashes(values(infos).map(getServerInfoHash));
+}
+
+function getServerInfoHash(info: RawThreadInfo) {
+  return hash(validateOutput(null, rawThreadInfoValidator, info));
+}
diff --git a/keyserver/src/shared/state-sync/users-state-sync-spec.js b/keyserver/src/shared/state-sync/users-state-sync-spec.js
--- a/keyserver/src/shared/state-sync/users-state-sync-spec.js
+++ b/keyserver/src/shared/state-sync/users-state-sync-spec.js
@@ -3,11 +3,13 @@
 import { usersStateSyncSpec as libSpec } from 'lib/shared/state-sync/users-state-sync-spec.js';
 import type { UserInconsistencyReportCreationRequest } from 'lib/types/report-types.js';
 import type { UserInfos, UserInfo } from 'lib/types/user-types.js';
-import { values } from 'lib/utils/objects.js';
+import { userInfoValidator } from 'lib/types/user-types.js';
+import { values, hash, combineUnorderedHashes } from 'lib/utils/objects.js';
 
 import type { ServerStateSyncSpec } from './state-sync-spec.js';
 import { fetchKnownUserInfos } from '../../fetchers/user-fetchers.js';
 import type { Viewer } from '../../session/viewer.js';
+import { validateOutput } from '../../utils/validation-utils.js';
 
 export const usersStateSyncSpec: ServerStateSyncSpec<
   UserInfos,
@@ -15,16 +17,32 @@
   UserInfo,
   $ReadOnlyArray<UserInconsistencyReportCreationRequest>,
 > = Object.freeze({
-  fetch(viewer: Viewer, ids?: $ReadOnlySet<string>) {
-    if (ids) {
-      return fetchKnownUserInfos(viewer, [...ids]);
-    }
-
-    return fetchKnownUserInfos(viewer);
-  },
+  fetch,
   async fetchFullSocketSyncPayload(viewer: Viewer) {
     const result = await fetchKnownUserInfos(viewer);
     return values(result);
   },
+  async fetchServerInfosHash(viewer: Viewer, ids?: $ReadOnlySet<string>) {
+    const infos = await fetch(viewer, ids);
+    return getServerInfosHash(infos);
+  },
+  getServerInfosHash,
+  getServerInfoHash,
   ...libSpec,
 });
+
+function fetch(viewer: Viewer, ids?: $ReadOnlySet<string>) {
+  if (ids) {
+    return fetchKnownUserInfos(viewer, [...ids]);
+  }
+
+  return fetchKnownUserInfos(viewer);
+}
+
+function getServerInfosHash(infos: UserInfos) {
+  return combineUnorderedHashes(values(infos).map(getServerInfoHash));
+}
+
+function getServerInfoHash(info: UserInfo) {
+  return hash(validateOutput(null, userInfoValidator, info));
+}
diff --git a/keyserver/src/socket/session-utils.js b/keyserver/src/socket/session-utils.js
--- a/keyserver/src/socket/session-utils.js
+++ b/keyserver/src/socket/session-utils.js
@@ -4,6 +4,10 @@
 import t from 'tcomb';
 import type { TUnion } from 'tcomb';
 
+import {
+  NEXT_CODE_VERSION,
+  hasMinCodeVersion,
+} from 'lib/shared/version-utils.js';
 import type { UpdateActivityResult } from 'lib/types/activity-types.js';
 import type { IdentityKeysBlob } from 'lib/types/crypto-types.js';
 import { isDeviceType } from 'lib/types/device-types.js';
@@ -386,8 +390,17 @@
       values(serverStateSyncSpecs).map(spec => [
         spec.hashKey,
         (async () => {
-          const data = await spec.fetch(viewer);
-          return hash(data);
+          if (
+            !hasMinCodeVersion(viewer.platformDetails, {
+              native: NEXT_CODE_VERSION,
+              web: NEXT_CODE_VERSION,
+            })
+          ) {
+            const data = await spec.fetch(viewer);
+            return hash(data);
+          }
+          const infosHash = await spec.fetchServerInfosHash(viewer);
+          return infosHash;
         })(),
       ]),
     );
@@ -452,7 +465,18 @@
       // out which infos don't match first
       const infos = fetchedData[key];
       for (const infoID in infos) {
-        hashesToCheck[`${innerHashKey}|${infoID}`] = hash(infos[infoID]);
+        let hashValue;
+        if (
+          hasMinCodeVersion(viewer.platformDetails, {
+            native: NEXT_CODE_VERSION,
+            web: NEXT_CODE_VERSION,
+          })
+        ) {
+          hashValue = spec.getServerInfoHash(infos[infoID]);
+        } else {
+          hashValue = hash(infos[infoID]);
+        }
+        hashesToCheck[`${innerHashKey}|${infoID}`] = hashValue;
       }
       failUnmentioned[key] = true;
     } else if (isTopLevelKey) {
diff --git a/lib/utils/objects.js b/lib/utils/objects.js
--- a/lib/utils/objects.js
+++ b/lib/utils/objects.js
@@ -73,6 +73,14 @@
   return stringHash(stableStringify(obj));
 }
 
+// This function doesn't look at the order of the hashes inside of the array
+// e.g `combineUnorderedHashes([1,2,3]) === combineUnorderedHashes([3,1,2])`
+// so it should only be used if the hashes include their ordering in them
+// somehow (e.g. `RawThreadInfo` contains `id`)
+function combineUnorderedHashes(hashes: $ReadOnlyArray<number>): number {
+  return hashes.reduce((a, v) => a ^ v, 0);
+}
+
 // returns an object with properties from obj1 not included in obj2
 function deepDiff<K, T>(
   obj1: NestedObjectMap<K, T>,
@@ -125,6 +133,7 @@
   findMaximumDepth,
   values,
   hash,
+  combineUnorderedHashes,
   assertObjectsAreEqual,
   deepDiff,
   entries,