diff --git a/services/commtest/src/identity/device.rs b/services/commtest/src/identity/device.rs
--- a/services/commtest/src/identity/device.rs
+++ b/services/commtest/src/identity/device.rs
@@ -43,6 +43,16 @@
 pub async fn register_user_device(
   keys: Option<&ClientPublicKeys>,
   device_type: Option<DeviceType>,
+) -> DeviceInfo {
+  register_user_device_with_device_list(keys, device_type, None).await
+}
+
+/// Same as [`register_user_device`] but with third param being a
+/// stringified signed device list JSON
+pub async fn register_user_device_with_device_list(
+  keys: Option<&ClientPublicKeys>,
+  device_type: Option<DeviceType>,
+  initial_device_list: Option<String>,
 ) -> DeviceInfo {
   let username: String = rand::thread_rng()
     .sample_iter(&Alphanumeric)
@@ -82,7 +92,7 @@
       device_type: device_type.into(),
     }),
     farcaster_id: None,
-    initial_device_list: "".to_string(),
+    initial_device_list: initial_device_list.unwrap_or_default(),
   };
 
   let mut identity_client = get_unauthenticated_client(
diff --git a/services/commtest/tests/identity_device_list_tests.rs b/services/commtest/tests/identity_device_list_tests.rs
--- a/services/commtest/tests/identity_device_list_tests.rs
+++ b/services/commtest/tests/identity_device_list_tests.rs
@@ -3,8 +3,8 @@
 use std::time::{SystemTime, UNIX_EPOCH};
 
 use commtest::identity::device::{
-  login_user_device, logout_user_device, register_user_device, DEVICE_TYPE,
-  PLACEHOLDER_CODE_VERSION,
+  login_user_device, logout_user_device, register_user_device,
+  register_user_device_with_device_list, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION,
 };
 use commtest::identity::SigningCapableAccount;
 use commtest::service_addr;
@@ -178,11 +178,11 @@
 
   // Perform unsigned update (add a new device)
   let first_update: DeviceListHistoryItem = {
-    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
-    let update_payload = SignedDeviceList::from_raw_unsigned(&RawDeviceList {
-      devices: vec![primary_device_id.clone(), "device2".to_string()],
-      timestamp: now.as_millis() as i64,
-    });
+    let update_payload =
+      SignedDeviceList::from_raw_unsigned(&RawDeviceList::new(vec![
+        primary_device_id.clone(),
+        "device2".to_string(),
+      ]));
     let update_request = UpdateDeviceListRequest::from(&update_payload);
     auth_client
       .update_device_list(update_request)
@@ -197,12 +197,8 @@
 
   // now perform a update (remove a device), but sign the device list
   let second_update: DeviceListHistoryItem = {
-    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
     let update_payload = SignedDeviceList::create_signed(
-      &RawDeviceList {
-        devices: vec![primary_device_id.clone()],
-        timestamp: now.as_millis() as i64,
-      },
+      &RawDeviceList::new(vec![primary_device_id.clone()]),
       &mut primary_account,
       None,
     );
@@ -220,12 +216,11 @@
 
   // now perform a signed update (add a device), but with invalid signature
   {
-    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
     let mut update_payload = SignedDeviceList::create_signed(
-      &RawDeviceList {
-        devices: vec![primary_device_id.clone(), "device3".to_string()],
-        timestamp: now.as_millis() as i64,
-      },
+      &RawDeviceList::new(vec![
+        primary_device_id.clone(),
+        "device3".to_string(),
+      ]),
       &mut primary_account,
       None,
     );
@@ -390,9 +385,61 @@
   }
 }
 
+#[tokio::test]
+async fn test_initial_device_list() {
+  // create signing account
+  let mut primary_account = SigningCapableAccount::new();
+  let primary_device_keys = primary_account.public_keys();
+  let primary_device_id = primary_device_keys.device_id();
+
+  // create initial device list
+  let raw_device_list = RawDeviceList::new(vec![primary_device_id]);
+  let signed_list = SignedDeviceList::create_signed(
+    &raw_device_list,
+    &mut primary_account,
+    None,
+  );
+
+  // register user with initial list
+  let user = register_user_device_with_device_list(
+    Some(&primary_device_keys),
+    Some(DeviceType::Ios),
+    Some(signed_list.as_json_string()),
+  )
+  .await;
+
+  let mut auth_client = get_auth_client(
+    &service_addr::IDENTITY_GRPC.to_string(),
+    user.user_id.clone(),
+    user.device_id,
+    user.access_token,
+    PLACEHOLDER_CODE_VERSION,
+    DEVICE_TYPE.to_string(),
+  )
+  .await
+  .expect("Couldn't connect to identity service");
+
+  let mut history =
+    get_device_list_history(&mut auth_client, &user.user_id).await;
+
+  let received_list =
+    history.pop().expect("Received empty device list history");
+
+  assert!(
+    history.is_empty(),
+    "Device list history should have no more updates"
+  );
+  assert_eq!(
+    received_list.cur_primary_signature, signed_list.cur_primary_signature,
+    "Signature mismatch"
+  );
+  assert!(received_list.last_primary_signature.is_none());
+  assert_eq!(received_list.into_raw(), raw_device_list);
+}
+
 // See GetDeviceListResponse in identity_authenticated.proto
 // for details on the response format.
-#[derive(Clone, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 #[serde(rename_all = "camelCase")]
 #[allow(unused)]
 struct RawDeviceList {
@@ -412,6 +459,14 @@
 }
 
 impl RawDeviceList {
+  fn new(devices: Vec<String>) -> Self {
+    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+    RawDeviceList {
+      devices,
+      timestamp: now.as_millis() as i64,
+    }
+  }
+
   fn as_json_string(&self) -> String {
     serde_json::to_string(self).expect("Failed to serialize RawDeviceList")
   }