diff --git a/keyserver/src/responders/responder-validators.test.js b/keyserver/src/responders/responder-validators.test.js
new file mode 100644
--- /dev/null
+++ b/keyserver/src/responders/responder-validators.test.js
@@ -0,0 +1,333 @@
+// @flow
+
+import {
+  logInResponseValidator,
+  registerResponseValidator,
+  logOutResponseValidator,
+} from './user-responders.js';
+
+describe('user responder validators', () => {
+  it('should validate logout response', () => {
+    const response = { currentUserInfo: { id: '93078', anonymous: true } };
+    expect(logOutResponseValidator.is(response)).toBe(true);
+    response.currentUserInfo.anonymous = false;
+    expect(logOutResponseValidator.is(response)).toBe(false);
+  });
+
+  it('should validate register response', () => {
+    const response = {
+      id: '93079',
+      rawMessageInfos: [
+        {
+          type: 1,
+          threadID: '93095',
+          creatorID: '93079',
+          time: 1682086407469,
+          initialThreadState: {
+            type: 6,
+            name: null,
+            parentThreadID: '1',
+            color: '648caa',
+            memberIDs: ['256', '93079'],
+          },
+          id: '93110',
+        },
+        {
+          type: 0,
+          threadID: '93095',
+          creatorID: '256',
+          time: 1682086407575,
+          text: 'welcome to Comm!',
+          id: '93113',
+        },
+      ],
+      currentUserInfo: { id: '93079', username: 'user' },
+      cookieChange: {
+        threadInfos: {
+          '1': {
+            id: '1',
+            type: 12,
+            name: 'GENESIS',
+            description: 'desc',
+            color: 'c85000',
+            creationTime: 1672934346213,
+            parentThreadID: null,
+            members: [
+              {
+                id: '256',
+                role: '83796',
+                permissions: {
+                  know_of: { value: true, source: '1' },
+                  membership: { value: false, source: null },
+                  visible: { value: true, source: '1' },
+                  voiced: { value: true, source: '1' },
+                  edit_entries: { value: true, source: '1' },
+                  edit_thread: { value: true, source: '1' },
+                  edit_thread_description: { value: true, source: '1' },
+                  edit_thread_color: { value: true, source: '1' },
+                  delete_thread: { value: true, source: '1' },
+                  create_subthreads: { value: true, source: '1' },
+                  create_sidebars: { value: true, source: '1' },
+                  join_thread: { value: false, source: null },
+                  edit_permissions: { value: false, source: null },
+                  add_members: { value: true, source: '1' },
+                  remove_members: { value: true, source: '1' },
+                  change_role: { value: true, source: '1' },
+                  leave_thread: { value: false, source: null },
+                  react_to_message: { value: true, source: '1' },
+                  edit_message: { value: true, source: '1' },
+                },
+                isSender: false,
+              },
+            ],
+            roles: {
+              '83795': {
+                id: '83795',
+                name: 'Members',
+                permissions: {
+                  know_of: true,
+                  visible: true,
+                  descendant_open_know_of: true,
+                  descendant_open_visible: true,
+                  descendant_opentoplevel_join_thread: true,
+                },
+                isDefault: true,
+              },
+            },
+            currentUser: {
+              role: '83795',
+              permissions: {
+                know_of: { value: true, source: '1' },
+                membership: { value: false, source: null },
+                visible: { value: true, source: '1' },
+                voiced: { value: false, source: null },
+                edit_entries: { value: false, source: null },
+                edit_thread: { value: false, source: null },
+                edit_thread_description: { value: false, source: null },
+                edit_thread_color: { value: false, source: null },
+                delete_thread: { value: false, source: null },
+                create_subthreads: { value: false, source: null },
+                create_sidebars: { value: false, source: null },
+                join_thread: { value: false, source: null },
+                edit_permissions: { value: false, source: null },
+                add_members: { value: false, source: null },
+                remove_members: { value: false, source: null },
+                change_role: { value: false, source: null },
+                leave_thread: { value: false, source: null },
+                react_to_message: { value: false, source: null },
+                edit_message: { value: false, source: null },
+              },
+              subscription: { home: true, pushNotifs: true },
+              unread: true,
+            },
+            repliesCount: 0,
+            containingThreadID: null,
+            community: null,
+          },
+        },
+        userInfos: [
+          { id: '5', username: 'commbot' },
+          { id: '256', username: 'ashoat' },
+          { id: '93079', username: 'temp_user7' },
+        ],
+      },
+    };
+
+    expect(registerResponseValidator.is(response)).toBe(true);
+    response.cookieChange.userInfos = undefined;
+    expect(registerResponseValidator.is(response)).toBe(false);
+  });
+
+  it('should validate login response', () => {
+    const response = {
+      currentUserInfo: { id: '93079', username: 'temp_user7' },
+      rawMessageInfos: [
+        {
+          type: 0,
+          id: '93115',
+          threadID: '93094',
+          time: 1682086407577,
+          creatorID: '5',
+          text: 'This is your private chat, where you can set',
+        },
+        {
+          type: 1,
+          id: '93111',
+          threadID: '93094',
+          time: 1682086407467,
+          creatorID: '93079',
+          initialThreadState: {
+            type: 7,
+            name: 'temp_user7',
+            parentThreadID: '1',
+            color: '575757',
+            memberIDs: ['93079'],
+          },
+        },
+      ],
+      truncationStatuses: { '93094': 'exhaustive', '93095': 'exhaustive' },
+      serverTime: 1682086579416,
+      userInfos: [
+        { id: '5', username: 'commbot' },
+        { id: '256', username: 'ashoat' },
+        { id: '93079', username: 'temp_user7' },
+      ],
+      cookieChange: {
+        threadInfos: {
+          '1': {
+            id: '1',
+            type: 12,
+            name: 'GENESIS',
+            description:
+              'This is the first community on Comm. In the future it will',
+            color: 'c85000',
+            creationTime: 1672934346213,
+            parentThreadID: null,
+            members: [
+              {
+                id: '256',
+                role: '83796',
+                permissions: {
+                  know_of: { value: true, source: '1' },
+                  membership: { value: false, source: null },
+                  visible: { value: true, source: '1' },
+                  voiced: { value: true, source: '1' },
+                  edit_entries: { value: true, source: '1' },
+                  edit_thread: { value: true, source: '1' },
+                  edit_thread_description: { value: true, source: '1' },
+                  edit_thread_color: { value: true, source: '1' },
+                  delete_thread: { value: true, source: '1' },
+                  create_subthreads: { value: true, source: '1' },
+                  create_sidebars: { value: true, source: '1' },
+                  join_thread: { value: false, source: null },
+                  edit_permissions: { value: false, source: null },
+                  add_members: { value: true, source: '1' },
+                  remove_members: { value: true, source: '1' },
+                  change_role: { value: true, source: '1' },
+                  leave_thread: { value: false, source: null },
+                  react_to_message: { value: true, source: '1' },
+                  edit_message: { value: true, source: '1' },
+                },
+                isSender: false,
+              },
+              {
+                id: '93079',
+                role: '83795',
+                permissions: {
+                  know_of: { value: true, source: '1' },
+                  membership: { value: false, source: null },
+                  visible: { value: true, source: '1' },
+                  voiced: { value: false, source: null },
+                  edit_entries: { value: false, source: null },
+                  edit_thread: { value: false, source: null },
+                  edit_thread_description: { value: false, source: null },
+                  edit_thread_color: { value: false, source: null },
+                  delete_thread: { value: false, source: null },
+                  create_subthreads: { value: false, source: null },
+                  create_sidebars: { value: false, source: null },
+                  join_thread: { value: false, source: null },
+                  edit_permissions: { value: false, source: null },
+                  add_members: { value: false, source: null },
+                  remove_members: { value: false, source: null },
+                  change_role: { value: false, source: null },
+                  leave_thread: { value: false, source: null },
+                  react_to_message: { value: false, source: null },
+                  edit_message: { value: false, source: null },
+                },
+                isSender: false,
+              },
+            ],
+            roles: {
+              '83795': {
+                id: '83795',
+                name: 'Members',
+                permissions: {
+                  know_of: true,
+                  visible: true,
+                  descendant_open_know_of: true,
+                  descendant_open_visible: true,
+                  descendant_opentoplevel_join_thread: true,
+                },
+                isDefault: true,
+              },
+              '83796': {
+                id: '83796',
+                name: 'Admins',
+                permissions: {
+                  know_of: true,
+                  visible: true,
+                  voiced: true,
+                  react_to_message: true,
+                  edit_message: true,
+                  edit_entries: true,
+                  edit_thread: true,
+                  edit_thread_color: true,
+                  edit_thread_description: true,
+                  create_subthreads: true,
+                  create_sidebars: true,
+                  add_members: true,
+                  delete_thread: true,
+                  remove_members: true,
+                  change_role: true,
+                  descendant_know_of: true,
+                  descendant_visible: true,
+                  descendant_toplevel_join_thread: true,
+                  child_join_thread: true,
+                  descendant_voiced: true,
+                  descendant_edit_entries: true,
+                  descendant_edit_thread: true,
+                  descendant_edit_thread_color: true,
+                  descendant_edit_thread_description: true,
+                  descendant_toplevel_create_subthreads: true,
+                  descendant_toplevel_create_sidebars: true,
+                  descendant_add_members: true,
+                  descendant_delete_thread: true,
+                  descendant_edit_permissions: true,
+                  descendant_remove_members: true,
+                  descendant_change_role: true,
+                },
+                isDefault: false,
+              },
+            },
+            currentUser: {
+              role: '83795',
+              permissions: {
+                know_of: { value: true, source: '1' },
+                membership: { value: false, source: null },
+                visible: { value: true, source: '1' },
+                voiced: { value: false, source: null },
+                edit_entries: { value: false, source: null },
+                edit_thread: { value: false, source: null },
+                edit_thread_description: { value: false, source: null },
+                edit_thread_color: { value: false, source: null },
+                delete_thread: { value: false, source: null },
+                create_subthreads: { value: false, source: null },
+                create_sidebars: { value: false, source: null },
+                join_thread: { value: false, source: null },
+                edit_permissions: { value: false, source: null },
+                add_members: { value: false, source: null },
+                remove_members: { value: false, source: null },
+                change_role: { value: false, source: null },
+                leave_thread: { value: false, source: null },
+                react_to_message: { value: false, source: null },
+                edit_message: { value: false, source: null },
+              },
+              subscription: { home: true, pushNotifs: true },
+              unread: true,
+            },
+            repliesCount: 0,
+            containingThreadID: null,
+            community: null,
+          },
+        },
+        userInfos: [],
+      },
+      rawEntryInfos: [],
+    };
+
+    expect(logInResponseValidator.is(response)).toBe(true);
+    expect(
+      logInResponseValidator.is({ ...response, currentUserInfo: undefined }),
+    ).toBe(false);
+  });
+});
diff --git a/keyserver/src/responders/user-responders.js b/keyserver/src/responders/user-responders.js
--- a/keyserver/src/responders/user-responders.js
+++ b/keyserver/src/responders/user-responders.js
@@ -3,10 +3,14 @@
 import type { Utility as OlmUtility } from '@commapp/olm';
 import invariant from 'invariant';
 import { ErrorTypes, SiweMessage } from 'siwe';
-import t from 'tcomb';
+import t, { type TInterface } from 'tcomb';
 import bcrypt from 'twin-bcrypt';
 
-import { baseLegalPolicies, policies } from 'lib/facts/policies.js';
+import {
+  baseLegalPolicies,
+  policies,
+  policyTypeValidator,
+} from 'lib/facts/policies.js';
 import { hasMinCodeVersion } from 'lib/shared/version-utils.js';
 import type {
   ResetPasswordRequest,
@@ -33,18 +37,33 @@
   IdentityKeysBlob,
   SignedIdentityKeysBlob,
 } from 'lib/types/crypto-types.js';
-import type { CalendarQuery } from 'lib/types/entry-types.js';
-import { defaultNumberPerThread } from 'lib/types/message-types.js';
+import {
+  type CalendarQuery,
+  rawEntryInfoValidator,
+} from 'lib/types/entry-types.js';
+import {
+  defaultNumberPerThread,
+  rawMessageInfoValidator,
+  messageTruncationStatusesValidator,
+} from 'lib/types/message-types.js';
 import type {
   SIWEAuthRequest,
   SIWEMessage,
   SIWESocialProof,
 } from 'lib/types/siwe-types.js';
-import type {
-  SubscriptionUpdateRequest,
-  SubscriptionUpdateResponse,
+import {
+  type SubscriptionUpdateRequest,
+  type SubscriptionUpdateResponse,
+  threadSubscriptionValidator,
 } from 'lib/types/subscription-types.js';
-import type { PasswordUpdate } from 'lib/types/user-types.js';
+import { rawThreadInfoValidator } from 'lib/types/thread-types.js';
+import {
+  type PasswordUpdate,
+  loggedOutUserInfoValidator,
+  loggedInUserInfoValidator,
+  oldLoggedInUserInfoValidator,
+  userInfoValidator,
+} from 'lib/types/user-types.js';
 import { updateUserAvatarRequestValidator } from 'lib/utils/avatar-utils.js';
 import {
   identityKeysBlobValidator,
@@ -66,6 +85,7 @@
   tEmail,
   tOldValidUsername,
   tRegex,
+  tID,
 } from 'lib/utils/validation-utils.js';
 
 import {
@@ -118,6 +138,11 @@
   }),
 });
 
+export const subscriptionUpdateResponseValidator: TInterface<SubscriptionUpdateResponse> =
+  tShape<SubscriptionUpdateResponse>({
+    threadSubscription: threadSubscriptionValidator,
+  });
+
 async function userSubscriptionUpdateResponder(
   viewer: Viewer,
   input: any,
@@ -163,6 +188,11 @@
   await checkAndSendPasswordResetEmail(request);
 }
 
+export const logOutResponseValidator: TInterface<LogOutResponse> =
+  tShape<LogOutResponse>({
+    currentUserInfo: loggedOutUserInfoValidator,
+  });
+
 async function logOutResponder(viewer: Viewer): Promise<LogOutResponse> {
   await validateInput(viewer, null, null);
   if (viewer.loggedIn) {
@@ -216,6 +246,20 @@
   signedIdentityKeysBlob: t.maybe(signedIdentityKeysBlobValidator),
 });
 
+export const registerResponseValidator: TInterface<RegisterResponse> =
+  tShape<RegisterResponse>({
+    id: t.String,
+    rawMessageInfos: t.list(rawMessageInfoValidator),
+    currentUserInfo: t.union([
+      oldLoggedInUserInfoValidator,
+      loggedInUserInfoValidator,
+    ]),
+    cookieChange: tShape({
+      threadInfos: t.dict(t.String, rawThreadInfoValidator),
+      userInfos: t.list(userInfoValidator),
+    }),
+  });
+
 async function accountCreationResponder(
   viewer: Viewer,
   input: any,
@@ -363,6 +407,24 @@
   signedIdentityKeysBlob: t.maybe(signedIdentityKeysBlobValidator),
 });
 
+export const logInResponseValidator: TInterface<LogInResponse> =
+  tShape<LogInResponse>({
+    currentUserInfo: t.union([
+      loggedInUserInfoValidator,
+      oldLoggedInUserInfoValidator,
+    ]),
+    rawMessageInfos: t.list(rawMessageInfoValidator),
+    truncationStatuses: messageTruncationStatusesValidator,
+    userInfos: t.list(userInfoValidator),
+    rawEntryInfos: t.maybe(t.list(rawEntryInfoValidator)),
+    serverTime: t.Number,
+    cookieChange: tShape({
+      threadInfos: t.dict(tID, rawThreadInfoValidator),
+      userInfos: t.list(userInfoValidator),
+    }),
+    notAcknowledgedPolicies: t.maybe(t.list(policyTypeValidator)),
+  });
+
 async function logInResponder(
   viewer: Viewer,
   input: any,
diff --git a/lib/facts/policies.js b/lib/facts/policies.js
--- a/lib/facts/policies.js
+++ b/lib/facts/policies.js
@@ -1,5 +1,7 @@
 // @flow
 
+import t, { type TEnums } from 'tcomb';
+
 import { values } from '../utils/objects.js';
 
 export const policyTypes = Object.freeze({
@@ -9,5 +11,6 @@
 export const policies: $ReadOnlyArray<string> = values(policyTypes);
 
 export type PolicyType = $Values<typeof policyTypes>;
+export const policyTypeValidator: TEnums = t.enums.of(policies);
 
 export const baseLegalPolicies = [policyTypes.tosAndPrivacyPolicy];
diff --git a/lib/types/account-types.js b/lib/types/account-types.js
--- a/lib/types/account-types.js
+++ b/lib/types/account-types.js
@@ -1,5 +1,7 @@
 // @flow
 
+import t, { type TInterface } from 'tcomb';
+
 import type { SignedIdentityKeysBlob } from './crypto-types.js';
 import type { PlatformDetails } from './device-types.js';
 import type {
@@ -7,21 +9,22 @@
   CalendarResult,
   RawEntryInfo,
 } from './entry-types.js';
-import type {
-  RawMessageInfo,
-  MessageTruncationStatuses,
-  GenericMessagesResult,
+import {
+  type RawMessageInfo,
+  type MessageTruncationStatuses,
+  type GenericMessagesResult,
 } from './message-types.js';
 import type { PreRequestUserState } from './session-types.js';
-import type { RawThreadInfo } from './thread-types.js';
-import type {
-  UserInfo,
-  LoggedOutUserInfo,
-  LoggedInUserInfo,
-  OldLoggedInUserInfo,
+import { type RawThreadInfo } from './thread-types.js';
+import {
+  type UserInfo,
+  type LoggedOutUserInfo,
+  type LoggedInUserInfo,
+  type OldLoggedInUserInfo,
 } from './user-types.js';
 import type { PolicyType } from '../facts/policies.js';
 import { values } from '../utils/objects.js';
+import { tShape } from '../utils/validation-utils.js';
 
 export type ResetPasswordRequest = {
   +usernameOrEmail: string,
@@ -175,10 +178,6 @@
   DEFAULT_NOTIFICATIONS: 'default_user_notifications',
 });
 
-export type DefaultNotificationPayload = {
-  +default_user_notifications: ?NotificationTypes,
-};
-
 export const notificationTypes = Object.freeze({
   FOCUSED: 'focused',
   BADGE_ONLY: 'badge_only',
@@ -189,3 +188,12 @@
 
 export const notificationTypeValues: $ReadOnlyArray<NotificationTypes> =
   values(notificationTypes);
+
+export type DefaultNotificationPayload = {
+  +default_user_notifications: ?NotificationTypes,
+};
+
+export const defaultNotificationPayloadValidator: TInterface<DefaultNotificationPayload> =
+  tShape<DefaultNotificationPayload>({
+    default_user_notifications: t.maybe(t.enums.of(notificationTypeValues)),
+  });
diff --git a/lib/types/message-types.js b/lib/types/message-types.js
--- a/lib/types/message-types.js
+++ b/lib/types/message-types.js
@@ -1,7 +1,12 @@
 // @flow
 
 import invariant from 'invariant';
-import t, { type TUnion, type TInterface } from 'tcomb';
+import t, {
+  type TUnion,
+  type TDict,
+  type TEnums,
+  type TInterface,
+} from 'tcomb';
 
 import { type ClientDBMediaInfo } from './media-types.js';
 import { messageTypes, type MessageType } from './message-types-enum.js';
@@ -133,6 +138,7 @@
 } from './messages/update-relationship.js';
 import { type RelativeUserInfo, type UserInfos } from './user-types.js';
 import type { CallServerEndpointResultInfoInterface } from '../utils/call-server-endpoint.js';
+import { values } from '../utils/objects.js';
 import { tNumber, tShape, tID } from '../utils/validation-utils.js';
 
 const composableMessageTypes = new Set([
@@ -532,9 +538,14 @@
   );
   return ourMessageTruncationStatus;
 }
+export const messageTruncationStatusValidator: TEnums = t.enums.of(
+  values(messageTruncationStatus),
+);
 export type MessageTruncationStatuses = {
   [threadID: string]: MessageTruncationStatus,
 };
+export const messageTruncationStatusesValidator: TDict<MessageTruncationStatuses> =
+  t.dict(tID, messageTruncationStatusValidator);
 
 export type ThreadCursors = { +[threadID: string]: ?string };
 
diff --git a/lib/types/relationship-types.js b/lib/types/relationship-types.js
--- a/lib/types/relationship-types.js
+++ b/lib/types/relationship-types.js
@@ -1,7 +1,10 @@
 // @flow
 
+import type { TRefinement } from 'tcomb';
+
 import type { AccountUserInfo } from './user-types.js';
 import { values } from '../utils/objects.js';
+import { tNumEnum } from '../utils/validation-utils.js';
 
 export const undirectedStatus = Object.freeze({
   KNOW_OF: 0,
@@ -24,6 +27,9 @@
   BOTH_BLOCKED: 6,
 });
 export type UserRelationshipStatus = $Values<typeof userRelationshipStatus>;
+export const userRelationshipStatusValidator: TRefinement<number> = tNumEnum(
+  values(userRelationshipStatus),
+);
 
 export const relationshipActions = Object.freeze({
   FRIEND: 'friend',
diff --git a/lib/types/user-types.js b/lib/types/user-types.js
--- a/lib/types/user-types.js
+++ b/lib/types/user-types.js
@@ -1,9 +1,18 @@
 // @flow
 
-import type { DefaultNotificationPayload } from './account-types.js';
-import type { ClientAvatar } from './avatar-types.js';
-import type { UserRelationshipStatus } from './relationship-types.js';
+import t, { type TInterface } from 'tcomb';
+
+import {
+  type DefaultNotificationPayload,
+  defaultNotificationPayloadValidator,
+} from './account-types.js';
+import { type ClientAvatar, clientAvatarValidator } from './avatar-types.js';
+import {
+  type UserRelationshipStatus,
+  userRelationshipStatusValidator,
+} from './relationship-types.js';
 import type { UserInconsistencyReportCreationRequest } from './report-types.js';
+import { tBool, tShape } from '../utils/validation-utils.js';
 
 export type GlobalUserInfo = {
   +id: string,
@@ -23,6 +32,12 @@
   +relationshipStatus?: UserRelationshipStatus,
   +avatar?: ?ClientAvatar,
 };
+export const userInfoValidator: TInterface<UserInfo> = tShape<UserInfo>({
+  id: t.String,
+  username: t.maybe(t.String),
+  relationshipStatus: t.maybe(userRelationshipStatusValidator),
+  avatar: t.maybe(clientAvatarValidator),
+});
 export type UserInfos = { +[id: string]: UserInfo };
 
 export type AccountUserInfo = {
@@ -50,6 +65,13 @@
   +email: string,
   +emailVerified: boolean,
 };
+export const oldLoggedInUserInfoValidator: TInterface<OldLoggedInUserInfo> =
+  tShape<OldLoggedInUserInfo>({
+    id: t.String,
+    username: t.String,
+    email: t.String,
+    emailVerified: t.Boolean,
+  });
 
 export type LoggedInUserInfo = {
   +id: string,
@@ -57,11 +79,20 @@
   +settings?: DefaultNotificationPayload,
   +avatar?: ?ClientAvatar,
 };
+export const loggedInUserInfoValidator: TInterface<LoggedInUserInfo> =
+  tShape<LoggedInUserInfo>({
+    id: t.String,
+    username: t.String,
+    settings: t.maybe(defaultNotificationPayloadValidator),
+    avatar: t.maybe(clientAvatarValidator),
+  });
 
 export type LoggedOutUserInfo = {
   +id: string,
   +anonymous: true,
 };
+export const loggedOutUserInfoValidator: TInterface<LoggedOutUserInfo> =
+  tShape<LoggedOutUserInfo>({ id: t.String, anonymous: tBool(true) });
 
 export type OldCurrentUserInfo = OldLoggedInUserInfo | LoggedOutUserInfo;
 export type CurrentUserInfo = LoggedInUserInfo | LoggedOutUserInfo;