diff --git a/services/identity/src/service.rs b/services/identity/src/service.rs
--- a/services/identity/src/service.rs
+++ b/services/identity/src/service.rs
@@ -13,7 +13,7 @@
 use rand::rngs::OsRng;
 use rand::{CryptoRng, Rng};
 use siwe::Message;
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
 use std::pin::Pin;
 use tokio::sync::mpsc;
 use tokio_stream::{wrappers::ReceiverStream, StreamExt};
@@ -44,12 +44,13 @@
   registration_response::Data::PakeLoginResponse as PakeRegistrationLoginResponse,
   registration_response::Data::PakeRegistrationResponse, CompareUsersRequest,
   CompareUsersResponse, DeleteUserRequest, DeleteUserResponse,
-  GenerateNonceRequest, GenerateNonceResponse, GetUserIdRequest,
-  GetUserIdResponse, LoginRequest, LoginResponse,
+  GenerateNonceRequest, GenerateNonceResponse,
+  GetSessionInitializationInfoRequest, GetSessionInitializationInfoResponse,
+  GetUserIdRequest, GetUserIdResponse, LoginRequest, LoginResponse,
   PakeLoginRequest as PakeLoginRequestStruct,
   PakeLoginResponse as PakeLoginResponseStruct, RegistrationRequest,
-  RegistrationResponse, VerifyUserTokenRequest, VerifyUserTokenResponse,
-  WalletLoginRequest as WalletLoginRequestStruct,
+  RegistrationResponse, SessionInitializationInfo, VerifyUserTokenRequest,
+  VerifyUserTokenResponse, WalletLoginRequest as WalletLoginRequestStruct,
   WalletLoginResponse as WalletLoginResponseStruct,
 };
 
@@ -269,6 +270,31 @@
       Err(e) => Err(handle_db_error(e)),
     }
   }
+
+  #[instrument(skip(self))]
+  async fn get_session_initialization_info(
+    &self,
+    request: Request<GetSessionInitializationInfoRequest>,
+  ) -> Result<Response<GetSessionInitializationInfoResponse>, Status> {
+    let message = request.into_inner();
+    match self
+      .client
+      .get_session_initialization_info(&message.user_id)
+      .await
+    {
+      Ok(Some(session_initialization_info)) => {
+        let mut devices = HashMap::new();
+        for (device, info) in session_initialization_info {
+          devices.insert(device, SessionInitializationInfo { info });
+        }
+        Ok(Response::new(GetSessionInitializationInfoResponse {
+          devices,
+        }))
+      }
+      Ok(None) => return Err(Status::not_found("user not found")),
+      Err(e) => Err(handle_db_error(e)),
+    }
+  }
 }
 
 async fn put_token_helper(
diff --git a/shared/protos/identity.proto b/shared/protos/identity.proto
--- a/shared/protos/identity.proto
+++ b/shared/protos/identity.proto
@@ -22,6 +22,10 @@
   rpc CompareUsers(CompareUsersRequest) returns (CompareUsersResponse) {}
   // Called by clients to get a nonce for a Sign-In with Ethereum message
   rpc GenerateNonce(GenerateNonceRequest) returns (GenerateNonceResponse) {}
+  // Called by clients to get session initialization info needed to open a new
+  // channel of communication with a given user
+  rpc GetSessionInitializationInfo(GetSessionInitializationInfoRequest) returns
+    (GetSessionInitializationInfoResponse) {}
 }
 
 // Helper types
@@ -81,6 +85,14 @@
   string accessToken = 1;
 }
 
+message SessionInitializationInfo {
+  // Initially, the key-value pairs will be as follows:
+  // payload -> stringified JSON containing primary and notification public keys
+  // signature -> above payload signed with the signing ed25519 key
+  // socialProof -> a signed message used for SIWE (optional)
+  map<string, string> info = 1;
+}
+
 // RegisterUser
 
 message RegistrationRequest {
@@ -177,3 +189,14 @@
 message GenerateNonceResponse{
   string nonce = 1;
 }
+
+// GetSessionInitializationInfo
+
+message GetSessionInitializationInfoRequest {
+  string userID = 1;
+}
+
+message GetSessionInitializationInfoResponse {
+  // Map is keyed on devices' public ed25519 key used for signing
+  map<string, SessionInitializationInfo> devices = 1;
+}