diff --git a/lib/tunnelbroker/tunnelbroker-context.js b/lib/tunnelbroker/tunnelbroker-context.js
--- a/lib/tunnelbroker/tunnelbroker-context.js
+++ b/lib/tunnelbroker/tunnelbroker-context.js
@@ -15,6 +15,10 @@
   tunnelbrokerMessageTypes,
   tunnelbrokerMessageValidator,
 } from '../types/tunnelbroker/messages.js';
+import {
+  type PeerToPeerMessage,
+  peerToPeerMessageValidator,
+} from '../types/tunnelbroker/peer-to-peer-message-types.js';
 import type { ConnectionInitializationMessage } from '../types/tunnelbroker/session-types.js';
 
 export type ClientMessageToDevice = {
@@ -45,10 +49,11 @@
 type Props = {
   +children: React.Node,
   +initMessage: ?ConnectionInitializationMessage,
+  +peerToPeerMessageHandler?: (message: PeerToPeerMessage) => mixed,
 };
 
 function TunnelbrokerProvider(props: Props): React.Node {
-  const { children, initMessage } = props;
+  const { children, initMessage, peerToPeerMessageHandler } = props;
   const [connected, setConnected] = React.useState(false);
   const listeners = React.useRef<Set<TunnelbrokerSocketListener>>(new Set());
   const socket = React.useRef<?WebSocket>(null);
@@ -137,6 +142,28 @@
           messageIDs: [message.messageID],
         };
         socket.current?.send(JSON.stringify(confirmation));
+
+        if (!peerToPeerMessageHandler) {
+          return;
+        }
+
+        let rawPeerToPeerMessage;
+        try {
+          rawPeerToPeerMessage = JSON.parse(message.payload);
+        } catch (e) {
+          console.log(
+            'error while parsing Tunnelbroker peer-to-peer message:',
+            e.message,
+          );
+          return;
+        }
+
+        if (!peerToPeerMessageValidator.is(rawPeerToPeerMessage)) {
+          console.log('invalid Tunnelbroker PeerToPeerMessage');
+          return;
+        }
+        const peerToPeerMessage: PeerToPeerMessage = rawPeerToPeerMessage;
+        peerToPeerMessageHandler(peerToPeerMessage);
       } else if (
         message.type ===
         tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE_REQUEST_STATUS
@@ -163,7 +190,13 @@
     };
 
     socket.current = tunnelbrokerSocket;
-  }, [connected, initMessage, resetHeartbeatTimeout, stopHeartbeatTimeout]);
+  }, [
+    connected,
+    initMessage,
+    resetHeartbeatTimeout,
+    stopHeartbeatTimeout,
+    peerToPeerMessageHandler,
+  ]);
 
   const sendMessage: (message: ClientMessageToDevice) => Promise<void> =
     React.useCallback(
diff --git a/lib/types/tunnelbroker/peer-to-peer-message-types.js b/lib/types/tunnelbroker/peer-to-peer-message-types.js
new file mode 100644
--- /dev/null
+++ b/lib/types/tunnelbroker/peer-to-peer-message-types.js
@@ -0,0 +1,68 @@
+// @flow
+
+import type { TInterface, TUnion } from 'tcomb';
+import t from 'tcomb';
+
+import { tShape, tString } from '../../utils/validation-utils.js';
+
+export type SenderInfo = {
+  +userID: string,
+  +deviceID: string,
+};
+const senderInfoValidator: TInterface<SenderInfo> = tShape<SenderInfo>({
+  userID: t.String,
+  deviceID: t.String,
+});
+
+export const peerToPeerMessageTypes = Object.freeze({
+  OUTBOUND_SESSION_CREATION: 'OutboundSessionCreation',
+  ENCRYPTED_MESSAGE: 'EncryptedMessage',
+  REFRESH_KEY_REQUEST: 'RefreshKeyRequest',
+});
+
+export type OutboundSessionCreation = {
+  +type: 'OutboundSessionCreation',
+  +senderInfo: SenderInfo,
+  +encryptedContent: string,
+};
+export const outboundSessionCreationValidator: TInterface<OutboundSessionCreation> =
+  tShape<OutboundSessionCreation>({
+    type: tString(peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION),
+    senderInfo: senderInfoValidator,
+    encryptedContent: t.String,
+  });
+
+export type EncryptedMessage = {
+  +type: 'EncryptedMessage',
+  +senderInfo: SenderInfo,
+  +encryptedContent: string,
+};
+export const encryptedMessageValidator: TInterface<EncryptedMessage> =
+  tShape<EncryptedMessage>({
+    type: tString(peerToPeerMessageTypes.ENCRYPTED_MESSAGE),
+    senderInfo: senderInfoValidator,
+    encryptedContent: t.String,
+  });
+
+export type RefreshKeyRequest = {
+  +type: 'RefreshKeyRequest',
+  +deviceID: string,
+  +numberOfKeys: number,
+};
+export const refreshKeysRequestValidator: TInterface<RefreshKeyRequest> =
+  tShape<RefreshKeyRequest>({
+    type: tString(peerToPeerMessageTypes.REFRESH_KEY_REQUEST),
+    deviceID: t.String,
+    numberOfKeys: t.Number,
+  });
+
+export type PeerToPeerMessage =
+  | OutboundSessionCreation
+  | EncryptedMessage
+  | RefreshKeyRequest;
+
+export const peerToPeerMessageValidator: TUnion<PeerToPeerMessage> = t.union([
+  outboundSessionCreationValidator,
+  encryptedMessageValidator,
+  refreshKeysRequestValidator,
+]);
diff --git a/native/handlers/peer-to-peer-message-handler.js b/native/handlers/peer-to-peer-message-handler.js
new file mode 100644
--- /dev/null
+++ b/native/handlers/peer-to-peer-message-handler.js
@@ -0,0 +1,18 @@
+// @flow
+
+import {
+  type PeerToPeerMessage,
+  peerToPeerMessageTypes,
+} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js';
+
+async function peerToPeerMessageHandler(
+  message: PeerToPeerMessage,
+): Promise<void> {
+  if (message.type === peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION) {
+    console.log('Received session creation request');
+  } else if (message.type === peerToPeerMessageTypes.ENCRYPTED_MESSAGE) {
+    console.log('Received encrypted message');
+  }
+}
+
+export { peerToPeerMessageHandler };
diff --git a/native/root.react.js b/native/root.react.js
--- a/native/root.react.js
+++ b/native/root.react.js
@@ -44,6 +44,7 @@
 import ConnectedStatusBar from './connected-status-bar.react.js';
 import { SQLiteDataHandler } from './data/sqlite-data-handler.js';
 import ErrorBoundary from './error-boundary.react.js';
+import { peerToPeerMessageHandler } from './handlers/peer-to-peer-message-handler.js';
 import InputStateContainer from './input/input-state-container.react.js';
 import LifecycleHandler from './lifecycle/lifecycle-handler.react.js';
 import MarkdownContextProvider from './markdown/markdown-context-provider.react.js';
@@ -295,7 +296,10 @@
   return (
     <GestureHandlerRootView style={styles.app}>
       <StaffContextProvider>
-        <TunnelbrokerProvider initMessage={tunnelbrokerInitMessage}>
+        <TunnelbrokerProvider
+          initMessage={tunnelbrokerInitMessage}
+          peerToPeerMessageHandler={peerToPeerMessageHandler}
+        >
           <FeatureFlagsProvider>
             <NavContext.Provider value={navContext}>
               <RootContext.Provider value={rootContext}>