diff --git a/keyserver/src/responders/website-responders.js b/keyserver/src/responders/website-responders.js
--- a/keyserver/src/responders/website-responders.js
+++ b/keyserver/src/responders/website-responders.js
@@ -341,6 +341,7 @@
     deviceToken: undefined,
     dataLoaded: viewer.loggedIn,
     windowActive: true,
+    userPolicies: {},
     _persist: null,
   };
 
diff --git a/lib/actions/user-actions.js b/lib/actions/user-actions.js
--- a/lib/actions/user-actions.js
+++ b/lib/actions/user-actions.js
@@ -8,6 +8,7 @@
   RegisterResult,
   RegisterInfo,
   UpdateUserSettingsRequest,
+  PolicyAcknowledgmentRequest,
 } from '../types/account-types';
 import type { GetSessionPublicKeysArgs } from '../types/request-types';
 import type { UserSearchResult } from '../types/search-types';
@@ -222,6 +223,19 @@
   return await callServerEndpoint('get_session_public_keys', data);
 };
 
+const policyAcknowledgmentActionTypes = Object.freeze({
+  started: 'POLICY_ACKNOWLEDGMENT_STARTED',
+  success: 'POLICY_ACKNOWLEDGMENT_SUCCESS',
+  failed: 'POLICY_ACKNOWLEDGMENT_FAILED',
+});
+const policyAcknowledgment = (
+  callServerEndpoint: CallServerEndpoint,
+): ((
+  policyRequest: PolicyAcknowledgmentRequest,
+) => Promise<void>) => async policyRequest => {
+  await callServerEndpoint('policy_acknowledgment', policyRequest);
+};
+
 export {
   changeUserPasswordActionTypes,
   changeUserPassword,
@@ -240,4 +254,6 @@
   setUserSettingsActionTypes,
   updateSubscription,
   updateSubscriptionActionTypes,
+  policyAcknowledgment,
+  policyAcknowledgmentActionTypes,
 };
diff --git a/lib/reducers/master-reducer.js b/lib/reducers/master-reducer.js
--- a/lib/reducers/master-reducer.js
+++ b/lib/reducers/master-reducer.js
@@ -19,6 +19,7 @@
 import reduceNextLocalID from './local-id-reducer';
 import { reduceMessageStore } from './message-reducer';
 import reduceBaseNavInfo from './nav-reducer';
+import policiesReducer from './policies-reducer.js';
 import reduceReportStore from './report-store-reducer';
 import { reduceThreadInfos } from './thread-reducer';
 import reduceUpdatesCurrentAsOf from './updates-reducer';
@@ -105,6 +106,7 @@
       ),
       nextLocalID: reduceNextLocalID(state.nextLocalID, action),
       dataLoaded: reduceDataLoaded(state.dataLoaded, action),
+      userPolicies: policiesReducer(state.userPolicies, action),
     },
     storeOperations: {
       draftStoreOperations,
diff --git a/lib/reducers/policies-reducer.js b/lib/reducers/policies-reducer.js
new file mode 100644
--- /dev/null
+++ b/lib/reducers/policies-reducer.js
@@ -0,0 +1,40 @@
+// @flow
+
+import { policyAcknowledgmentActionTypes } from '../actions/user-actions.js';
+import type { PolicyType } from '../facts/policies.js';
+import {
+  type UserPolicies,
+  forcePolicyAcknowledgmentActionType,
+} from '../types/policy-types.js';
+import type { BaseAction } from '../types/redux-types';
+
+function policiesReducer(
+  state: UserPolicies,
+  action: BaseAction,
+): UserPolicies {
+  if (action.type === forcePolicyAcknowledgmentActionType) {
+    const { notAcknowledgedPolicies } = action.payload;
+    const newState = { ...state };
+
+    notAcknowledgedPolicies.forEach((policy: PolicyType) => {
+      newState[policy] = {
+        ...newState[policy],
+        isAcknowledged: false,
+      };
+    });
+    return newState;
+  }
+  if (action.type === policyAcknowledgmentActionTypes.success) {
+    const { policy } = action.payload;
+    return {
+      ...state,
+      [policy]: {
+        ...state[policy],
+        isAcknowledged: true,
+      },
+    };
+  }
+  return state;
+}
+
+export default policiesReducer;
diff --git a/lib/types/policy-types.js b/lib/types/policy-types.js
--- a/lib/types/policy-types.js
+++ b/lib/types/policy-types.js
@@ -6,3 +6,22 @@
   +policy: PolicyType,
   +confirmed: boolean,
 };
+
+export type UserPolicyState = {
+  +isAcknowledged: boolean,
+};
+
+export type UserPolicies = {
+  +[name: PolicyType]: UserPolicyState,
+};
+
+export type ForcePolicyAcknowledgmentPayload = {
+  +notAcknowledgedPolicies: $ReadOnlyArray<PolicyType>,
+};
+
+export type PolicyAcknowledgmentPayload = {
+  +policy: PolicyType,
+};
+
+export const forcePolicyAcknowledgmentActionType =
+  'FORCE_POLICY_ACKNOWLEDGMENT';
diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js
--- a/lib/types/redux-types.js
+++ b/lib/types/redux-types.js
@@ -49,6 +49,11 @@
 } from './message-types';
 import type { RawTextMessageInfo } from './messages/text';
 import type { BaseNavInfo } from './nav-types';
+import {
+  type ForcePolicyAcknowledgmentPayload,
+  type PolicyAcknowledgmentPayload,
+  type UserPolicies,
+} from './policy-types.js';
 import type { RelationshipErrors } from './relationship-types';
 import type {
   EnabledReports,
@@ -97,6 +102,7 @@
   reportStore: ReportStore,
   nextLocalID: number,
   dataLoaded: boolean,
+  userPolicies: UserPolicies,
   ...
 };
 
@@ -828,6 +834,27 @@
       +error: true,
       +payload: Error,
       +loadingInfo: LoadingInfo,
+    }
+  | {
+      +type: 'FORCE_POLICY_ACKNOWLEDGMENT',
+      +payload: ForcePolicyAcknowledgmentPayload,
+      +loadingInfo: LoadingInfo,
+    }
+  | {
+      +type: 'POLICY_ACKNOWLEDGMENT_STARTED',
+      +payload?: void,
+      +loadingInfo: LoadingInfo,
+    }
+  | {
+      +type: 'POLICY_ACKNOWLEDGMENT_SUCCESS',
+      +payload: PolicyAcknowledgmentPayload,
+      +loadingInfo: LoadingInfo,
+    }
+  | {
+      +type: 'POLICY_ACKNOWLEDGMENT_FAILED',
+      +error: true,
+      +payload: Error,
+      +loadingInfo: LoadingInfo,
     };
 
 export type ActionPayload = ?(Object | Array<*> | $ReadOnlyArray<*> | string);
diff --git a/native/redux/redux-setup.js b/native/redux/redux-setup.js
--- a/native/redux/redux-setup.js
+++ b/native/redux/redux-setup.js
@@ -130,6 +130,7 @@
   deviceCameraInfo: defaultDeviceCameraInfo,
   deviceOrientation: Orientation.getInitialOrientation(),
   frozen: false,
+  userPolicies: {},
 }: AppState);
 
 function reducer(state: AppState = defaultState, action: Action) {
diff --git a/native/redux/state-types.js b/native/redux/state-types.js
--- a/native/redux/state-types.js
+++ b/native/redux/state-types.js
@@ -10,6 +10,7 @@
 import type { LifecycleState } from 'lib/types/lifecycle-state-types';
 import type { LoadingStatus } from 'lib/types/loading-types';
 import type { MessageStore } from 'lib/types/message-types';
+import type { UserPolicies } from 'lib/types/policy-types';
 import type { ReportStore } from 'lib/types/report-types';
 import type { ConnectionInfo } from 'lib/types/socket-types';
 import type { ThreadStore } from 'lib/types/thread-types';
@@ -55,4 +56,5 @@
   deviceCameraInfo: DeviceCameraInfo,
   deviceOrientation: Orientations,
   frozen: boolean,
+  userPolicies: UserPolicies,
 };
diff --git a/web/redux/redux-setup.js b/web/redux/redux-setup.js
--- a/web/redux/redux-setup.js
+++ b/web/redux/redux-setup.js
@@ -19,6 +19,7 @@
 import type { LifecycleState } from 'lib/types/lifecycle-state-types';
 import type { LoadingStatus } from 'lib/types/loading-types';
 import type { MessageStore } from 'lib/types/message-types';
+import type { UserPolicies } from 'lib/types/policy-types.js';
 import type { BaseAction } from 'lib/types/redux-types';
 import type { ReportStore } from 'lib/types/report-types';
 import type { ConnectionInfo } from 'lib/types/socket-types';
@@ -67,6 +68,7 @@
   userAgent: ?string,
   dataLoaded: boolean,
   windowActive: boolean,
+  userPolicies: UserPolicies,
   _persist: ?PersistState,
 };