diff --git a/datastore/src/main/java/net/jami/datastore/dao/ConversationDao.java b/datastore/src/main/java/net/jami/datastore/dao/ConversationDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..b115d4f567e3d6761c2ffdd4a97ccf2f5d8040d8
--- /dev/null
+++ b/datastore/src/main/java/net/jami/datastore/dao/ConversationDao.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.jami.datastore.dao;
+
+import lombok.extern.slf4j.Slf4j;
+
+import net.jami.datastore.main.DataStore;
+import net.jami.jams.common.dao.connectivity.SQLConnection;
+import net.jami.jams.common.objects.conversations.Conversation;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.List;
+
+@Slf4j
+public class ConversationDao extends AbstractDao<Conversation> {
+
+    public ConversationDao() {
+        this.setTableName("conversations");
+        this.setTClass(Conversation.class);
+    }
+
+    public List<Conversation> getByOwner(String owner) {
+        ResultSet rs = executeQuery("SELECT * FROM conversations WHERE owner = ?", owner);
+        return getObjectsFromResultSet(rs);
+    }
+
+    @Override
+    public boolean storeObject(Conversation object) {
+        String query =
+                "INSERT INTO conversations (owner, id, created, removed, erased, members, lastDisplayed)"
+                        + " VALUES (?, ?, ?, ?, ?, ?, ?)";
+        return executeInsert(query, object);
+    }
+
+    public boolean storeConversationList(List<Conversation> conversationList) {
+        if (conversationList.isEmpty()) {
+            log.error("Cannot store empty conversation list");
+            return false;
+        }
+
+        SQLConnection connection = DataStore.connectionPool.getConnection();
+        try {
+            // Initiate transaction
+            connection.getConnection().setAutoCommit(false);
+            String update =
+                    "UPDATE conversations SET created = ?, removed = ?, erased = ?, members = ?, lastDisplayed = ?"
+                            + "WHERE owner = ? AND id = ?";
+
+            String insert =
+                    "INSERT INTO conversations (owner, id, created, removed, erased, members, lastDisplayed) VALUES "
+                            + "(?, ?, ?, ?, ?, ?, ?)";
+            for (Conversation conversation : conversationList) {
+                PreparedStatement updatePs = connection.getConnection().prepareStatement(update);
+                conversation.getUpdate(updatePs);
+                int rowsUpdated = updatePs.executeUpdate();
+
+                if (rowsUpdated == 0) {
+                    // If no rows were updated, perform an insert
+                    PreparedStatement insertPs =
+                            connection.getConnection().prepareStatement(insert);
+                    conversation.getInsert(insertPs);
+                    insertPs.executeUpdate();
+                }
+            }
+            // Commit transaction
+            connection.getConnection().commit();
+            return true;
+        } catch (Exception e) {
+            log.error("Could not update conversations: {}", e.getMessage());
+            return false;
+        } finally {
+            DataStore.connectionPool.returnConnection(connection);
+        }
+    }
+}
diff --git a/datastore/src/main/java/net/jami/datastore/dao/ConversationRequestDao.java b/datastore/src/main/java/net/jami/datastore/dao/ConversationRequestDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3f568cce86f795000a3d9c859bbc30be7afc288
--- /dev/null
+++ b/datastore/src/main/java/net/jami/datastore/dao/ConversationRequestDao.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package net.jami.datastore.dao;
+
+import lombok.extern.slf4j.Slf4j;
+
+import net.jami.datastore.main.DataStore;
+import net.jami.jams.common.dao.connectivity.SQLConnection;
+import net.jami.jams.common.objects.conversations.ConversationRequest;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.List;
+
+@Slf4j
+public class ConversationRequestDao extends AbstractDao<ConversationRequest> {
+
+    public ConversationRequestDao() {
+        this.setTableName("conversations");
+        this.setTClass(ConversationRequest.class);
+    }
+
+    public List<ConversationRequest> getByOwner(String owner) {
+        ResultSet rs = executeQuery("SELECT * FROM conversation_requests WHERE owner = ?", owner);
+        return getObjectsFromResultSet(rs);
+    }
+
+    @Override
+    public boolean storeObject(ConversationRequest object) {
+        String query =
+                "INSERT INTO conversation_requests (owner, conversationId, sender, metadatas, received, declined)"
+                        + " VALUES (?, ?, ?, ?, ?, ?)";
+        return executeInsert(query, object);
+    }
+
+    public boolean storeConversationRequestList(List<ConversationRequest> conversationRequestList) {
+        if (conversationRequestList.isEmpty()) {
+            log.error("Cannot store empty conversationRequest list");
+            return false;
+        }
+
+        SQLConnection connection = DataStore.connectionPool.getConnection();
+        try {
+            // Initiate transaction
+            connection.getConnection().setAutoCommit(false);
+            String update =
+                    "UPDATE conversation_requests SET sender = ?, metadatas = ?, received = ?, declined = ?"
+                            + "WHERE owner = ? AND conversationId = ?";
+
+            String insert =
+                    "INSERT INTO conversation_requests (owner, conversationId, sender, metadatas, received, declined) VALUES "
+                            + "(?, ?, ?, ?, ?, ?)";
+            for (ConversationRequest conversationRequest : conversationRequestList) {
+                PreparedStatement updatePs = connection.getConnection().prepareStatement(update);
+                conversationRequest.getUpdate(updatePs);
+                int rowsUpdated = updatePs.executeUpdate();
+
+                if (rowsUpdated == 0) {
+                    // If no rows were updated, perform an insert
+                    PreparedStatement insertPs =
+                            connection.getConnection().prepareStatement(insert);
+                    conversationRequest.getInsert(insertPs);
+                    insertPs.executeUpdate();
+                }
+            }
+            // Commit transaction
+            connection.getConnection().commit();
+            return true;
+        } catch (Exception e) {
+            log.error("Could not update conversationRequests: {}", e.getMessage());
+            return false;
+        } finally {
+            DataStore.connectionPool.returnConnection(connection);
+        }
+    }
+}
diff --git a/datastore/src/main/java/net/jami/datastore/main/DataStore.java b/datastore/src/main/java/net/jami/datastore/main/DataStore.java
index 80e9491e47102073eea3d4a22227900c715ae015..08aba9c4e5b5a695bdd5230464666061f4eaaa97 100644
--- a/datastore/src/main/java/net/jami/datastore/main/DataStore.java
+++ b/datastore/src/main/java/net/jami/datastore/main/DataStore.java
@@ -26,6 +26,8 @@ import lombok.Getter;
 import lombok.Setter;
 
 import net.jami.datastore.dao.ContactDao;
+import net.jami.datastore.dao.ConversationDao;
+import net.jami.datastore.dao.ConversationRequestDao;
 import net.jami.datastore.dao.DeviceDao;
 import net.jami.datastore.dao.GroupDao;
 import net.jami.datastore.dao.PolicyDao;
@@ -58,6 +60,8 @@ public class DataStore implements AuthenticationSource {
     private DeviceDao deviceDao;
     private SystemDao systemDao;
     private ContactDao contactDao;
+    private ConversationDao conversationDao;
+    private ConversationRequestDao conversationRequestDao;
     private UserProfileDao userProfileDao;
     private UserGroupMappingsDao userGroupMappingsDao;
     public static final Integer RESULTS_PER_PAGE = 24;
@@ -74,6 +78,8 @@ public class DataStore implements AuthenticationSource {
         deviceDao = new DeviceDao();
         systemDao = new SystemDao();
         contactDao = new ContactDao();
+        conversationDao = new ConversationDao();
+        conversationRequestDao = new ConversationRequestDao();
         userProfileDao = new UserProfileDao();
         userGroupMappingsDao = new UserGroupMappingsDao();
     }
diff --git a/datastore/src/main/resources/db/migration/V38__Conversations.sql b/datastore/src/main/resources/db/migration/V38__Conversations.sql
new file mode 100644
index 0000000000000000000000000000000000000000..0c55f54a139d68cd4cf1c34921d89041b1aacbd1
--- /dev/null
+++ b/datastore/src/main/resources/db/migration/V38__Conversations.sql
@@ -0,0 +1,4 @@
+CREATE TABLE conversations (owner varchar(255),
+id varchar(255), created bigint, removed bigint, 
+erased bigint, members clob, lastDisplayed varchar(255),
+PRIMARY KEY (owner,id));
\ No newline at end of file
diff --git a/datastore/src/main/resources/db/migration/V39__ConversationRequests.sql b/datastore/src/main/resources/db/migration/V39__ConversationRequests.sql
new file mode 100644
index 0000000000000000000000000000000000000000..3be4a5520ae32f1a74d548b3b1d278802aab9852
--- /dev/null
+++ b/datastore/src/main/resources/db/migration/V39__ConversationRequests.sql
@@ -0,0 +1,4 @@
+CREATE TABLE conversation_requests (owner varchar(255),
+conversationId varchar(255), sender varchar(255), metadatas clob,
+received bigint, declined bigint,
+PRIMARY KEY (owner,conversationId));
\ No newline at end of file
diff --git a/datastore/src/test/java/net/jami/datastore/dao/ConversationDaoTest.java b/datastore/src/test/java/net/jami/datastore/dao/ConversationDaoTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..47724c210359a45d72904e8b4007b7c2ac8f3377
--- /dev/null
+++ b/datastore/src/test/java/net/jami/datastore/dao/ConversationDaoTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package net.jami.datastore.dao;
+
+import net.jami.datastore.main.DataStore;
+import net.jami.jams.common.dao.connectivity.SQLConnection;
+import net.jami.jams.common.objects.conversations.Conversation;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+
+class ConversationDaoTest {
+    private ConversationDao conversationDao;
+
+    @BeforeEach
+    void setUp() {
+        DataStore dataStore = new DataStore("jdbc:derby:memory:testdb;create=true");
+        SQLConnection connection = DataStore.connectionPool.getConnection();
+        Assertions.assertNotNull(connection);
+
+        conversationDao = new ConversationDao();
+        Assertions.assertNotNull(conversationDao);
+    }
+
+    @Test
+    void getByOwner() throws Exception {
+        Assertions.assertTrue(conversationDao.getByOwner("test").isEmpty());
+    }
+
+    @Test
+    void storeObject() throws Exception {
+        Conversation conversation = new Conversation();
+        Assertions.assertFalse(conversationDao.storeObject(conversation));
+    }
+
+    @Test
+    void storeCoList() throws Exception {
+        ArrayList<Conversation> conversationList = new ArrayList<>();
+        Assertions.assertFalse(conversationDao.storeConversationList(conversationList));
+
+        Conversation conversation = new Conversation();
+        conversationList.add(conversation);
+        Assertions.assertFalse(conversationDao.storeConversationList(conversationList));
+    }
+}
diff --git a/datastore/src/test/java/net/jami/datastore/dao/ConversationRequestDaoTest.java b/datastore/src/test/java/net/jami/datastore/dao/ConversationRequestDaoTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..37162822d76d75b4fc7c4502de4b7fa7fe8ae997
--- /dev/null
+++ b/datastore/src/test/java/net/jami/datastore/dao/ConversationRequestDaoTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package net.jami.datastore.dao;
+
+import net.jami.datastore.main.DataStore;
+import net.jami.jams.common.dao.connectivity.SQLConnection;
+import net.jami.jams.common.objects.conversations.ConversationRequest;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+
+class ConversationRequestDaoTest {
+    private ConversationRequestDao conversationRequestDao;
+
+    @BeforeEach
+    void setUp() {
+        DataStore dataStore = new DataStore("jdbc:derby:memory:testdb;create=true");
+        SQLConnection connection = DataStore.connectionPool.getConnection();
+        Assertions.assertNotNull(connection);
+
+        conversationRequestDao = new ConversationRequestDao();
+        Assertions.assertNotNull(conversationRequestDao);
+    }
+
+    @Test
+    void getByOwner() throws Exception {
+        Assertions.assertTrue(conversationRequestDao.getByOwner("test").isEmpty());
+    }
+
+    @Test
+    void storeObject() throws Exception {
+        ConversationRequest conversationRequest = new ConversationRequest();
+        Assertions.assertFalse(conversationRequestDao.storeObject(conversationRequest));
+    }
+
+    @Test
+    void storeConversationRequestList() throws Exception {
+        ArrayList<ConversationRequest> conversationList = new ArrayList<>();
+        Assertions.assertFalse(
+                conversationRequestDao.storeConversationRequestList(conversationList));
+
+        ConversationRequest conversationRequest = new ConversationRequest();
+        conversationList.add(conversationRequest);
+        Assertions.assertFalse(
+                conversationRequestDao.storeConversationRequestList(conversationList));
+    }
+}
diff --git a/jams-common/src/main/java/net/jami/jams/common/objects/conversations/Conversation.java b/jams-common/src/main/java/net/jami/jams/common/objects/conversations/Conversation.java
new file mode 100644
index 0000000000000000000000000000000000000000..19f9969cc3309e51464ef2ffd81baf4314dd34b8
--- /dev/null
+++ b/jams-common/src/main/java/net/jami/jams/common/objects/conversations/Conversation.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package net.jami.jams.common.objects.conversations;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import net.jami.jams.common.serialization.database.DatabaseObject;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@EqualsAndHashCode
+public class Conversation implements DatabaseObject {
+
+    private transient String owner;
+
+    private String id;
+    private Long created;
+    private Long removed;
+    private Long erased;
+    private String members;
+    private String lastDisplayed;
+
+    public Conversation(ResultSet rs) throws Exception {
+        this.owner = rs.getString("owner");
+        this.id = rs.getString("id");
+        this.created = rs.getLong("created");
+        this.removed = rs.getLong("removed");
+        this.erased = rs.getLong("erased");
+        this.members = rs.getString("members");
+
+        this.lastDisplayed = rs.getString("lastDisplayed");
+    }
+
+    @Override
+    public PreparedStatement getInsert(PreparedStatement ps) throws Exception {
+        ps.setString(1, owner);
+        ps.setString(2, id);
+        ps.setLong(3, created);
+        ps.setLong(4, removed);
+        ps.setLong(5, erased);
+        ps.setString(6, members);
+        ps.setString(7, lastDisplayed);
+        return ps;
+    }
+
+    @Override
+    public PreparedStatement getDelete(PreparedStatement ps) throws Exception {
+        return null;
+    }
+
+    @Override
+    public PreparedStatement getUpdate(PreparedStatement ps) throws Exception {
+        ps.setLong(1, created);
+        ps.setLong(2, removed);
+        ps.setLong(3, erased);
+        ps.setString(4, members);
+        ps.setString(5, lastDisplayed);
+        ps.setString(6, owner);
+        ps.setString(7, id);
+        return ps;
+    }
+}
diff --git a/jams-common/src/main/java/net/jami/jams/common/objects/conversations/ConversationRequest.java b/jams-common/src/main/java/net/jami/jams/common/objects/conversations/ConversationRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4df6495b13ba6dce49138589faa30e0c6eb86db3
--- /dev/null
+++ b/jams-common/src/main/java/net/jami/jams/common/objects/conversations/ConversationRequest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package net.jami.jams.common.objects.conversations;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import net.jami.jams.common.serialization.database.DatabaseObject;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@EqualsAndHashCode
+public class ConversationRequest implements DatabaseObject {
+
+    private transient String owner;
+
+    private String conversationId;
+    private String sender; // 'from' became sender because 'from' is a reserved keyword in SQL
+    private String metadatas;
+    private Long received;
+    private Long declined;
+
+    public ConversationRequest(ResultSet rs) throws Exception {
+        this.owner = rs.getString("owner");
+        this.conversationId = rs.getString("conversationId");
+        this.sender = rs.getString("sender");
+        this.metadatas = rs.getString("metadatas");
+        this.received = rs.getLong("received");
+        this.declined = rs.getLong("declined");
+    }
+
+    @Override
+    public PreparedStatement getInsert(PreparedStatement ps) throws Exception {
+        ps.setString(1, owner);
+        ps.setString(2, conversationId);
+        ps.setString(3, sender);
+        ps.setString(4, metadatas);
+        ps.setLong(5, received);
+        ps.setLong(6, declined);
+        return ps;
+    }
+
+    @Override
+    public PreparedStatement getDelete(PreparedStatement ps) throws Exception {
+        return null;
+    }
+
+    @Override
+    public PreparedStatement getUpdate(PreparedStatement ps) throws Exception {
+        ps.setString(1, sender);
+        ps.setString(2, metadatas);
+        ps.setLong(3, received);
+        ps.setLong(4, declined);
+        ps.setString(5, owner);
+        ps.setString(6, conversationId);
+        return ps;
+    }
+}
diff --git a/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/ContactAdapter.java b/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/ContactAdapter.java
index 67daa5865107e328606216831733143f6dc22bc8..ff6b3d9b58a061b91e34ae344b38151ab1ab0afd 100644
--- a/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/ContactAdapter.java
+++ b/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/ContactAdapter.java
@@ -1,3 +1,20 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
 package net.jami.jams.common.serialization.adapters;
 
 import com.google.gson.*;
diff --git a/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/ConversationAdapter.java b/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/ConversationAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8d15be1fd2f8356454d994750916c34ff56f0fa
--- /dev/null
+++ b/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/ConversationAdapter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package net.jami.jams.common.serialization.adapters;
+
+import com.google.gson.*;
+
+import net.jami.jams.common.objects.conversations.Conversation;
+
+import java.lang.reflect.Type;
+
+public class ConversationAdapter
+        implements JsonSerializer<Conversation>, JsonDeserializer<Conversation> {
+
+    /**
+     * {
+     * "created" : 1701180312,
+     * "erased" : 1701180454,
+     * "id" : "63afe87b8b18a3cfc40a4524ebd441c4a7d2336a",
+     * "lastDisplayed" : "63afe87b8b18a3cfc40a4524ebd441c4a7d2336a",
+     * "members" :
+     * [
+     * {
+     * "uri" : "7172ad8cf00c93235886d8ce1b2889638c0da68d"
+     * },
+     * {
+     * "uri" : "bf82b61537582a53fb0a28553f77060cc7a332c3"
+     * }
+     * ],
+     * "removed" : 1701180454
+     * }
+     */
+    @Override
+    public Conversation deserialize(
+            JsonElement json, Type typeOfT, JsonDeserializationContext context)
+            throws JsonParseException {
+        Gson gson = new Gson();
+        JsonObject input = json.getAsJsonObject();
+        Conversation conversation = new Conversation();
+        conversation.setId(input.get("id").getAsString());
+        conversation.setCreated(input.get("created").getAsLong());
+
+        long timeRemoved = 0L;
+        if (input.has("removed")) {
+            timeRemoved = input.get("removed").getAsLong();
+        }
+        conversation.setRemoved(timeRemoved);
+
+        long timeErased = 0L;
+        if (input.has("erased")) {
+            timeErased = input.get("erased").getAsLong();
+        }
+        conversation.setErased(timeErased);
+        if (input.has("members")) {
+            conversation.setMembers(gson.toJson(input.get("members")));
+        }
+        conversation.setLastDisplayed(input.get("lastDisplayed").getAsString());
+        return conversation;
+    }
+
+    @Override
+    public JsonElement serialize(
+            Conversation conversation, Type typeOfSrc, JsonSerializationContext context) {
+        JsonObject output = new JsonObject();
+        output.addProperty("id", conversation.getId());
+        output.addProperty("created", conversation.getCreated());
+        output.addProperty("removed", conversation.getRemoved());
+        output.addProperty("erased", conversation.getErased());
+        JsonElement jsonMembers = JsonParser.parseString(conversation.getMembers());
+        output.add("members", jsonMembers);
+        output.addProperty("lastDisplayed", conversation.getLastDisplayed());
+        return output;
+    }
+}
diff --git a/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/ConversationRequestAdapter.java b/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/ConversationRequestAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8cc05c518b4ab9bd55a96fbe6daf7339ee9e58e
--- /dev/null
+++ b/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/ConversationRequestAdapter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package net.jami.jams.common.serialization.adapters;
+
+import com.google.gson.*;
+
+import net.jami.jams.common.objects.conversations.ConversationRequest;
+
+import java.lang.reflect.Type;
+
+public class ConversationRequestAdapter
+        implements JsonSerializer<ConversationRequest>, JsonDeserializer<ConversationRequest> {
+
+    /**
+     * {
+     * "conversationId" : "b158323bd68c7f71f606e0a4fb505f59a8212afe",
+     * "from" : "89c5c8d61df665f9dde97a23666772bccb658c22",
+     * "metadatas" :
+     * {
+     * "avatar" : "",
+     * "title" : " "
+     * },
+     * "received" : 1701188208
+     * }
+     *
+     */
+    @Override
+    public ConversationRequest deserialize(
+            JsonElement json, Type typeOfT, JsonDeserializationContext context)
+            throws JsonParseException {
+        Gson gson = new Gson();
+        JsonObject input = json.getAsJsonObject();
+        ConversationRequest conversationRequest = new ConversationRequest();
+        conversationRequest.setConversationId(input.get("conversationId").getAsString());
+        conversationRequest.setSender(input.get("from").getAsString());
+
+        conversationRequest.setMetadatas(gson.toJson(input.get("metadatas")));
+
+        long timeReceived = 0L;
+        if (input.has("received")) {
+            timeReceived = input.get("received").getAsLong();
+        }
+        conversationRequest.setReceived(timeReceived);
+
+        long timeDeclined = 0L;
+        if (input.has("timeDeclined")) {
+            timeDeclined = input.get("timeDeclined").getAsLong();
+        }
+        conversationRequest.setDeclined(timeDeclined);
+        return conversationRequest;
+    }
+
+    @Override
+    public JsonElement serialize(
+            ConversationRequest conversationRequest,
+            Type typeOfSrc,
+            JsonSerializationContext context) {
+        JsonObject output = new JsonObject();
+        output.addProperty("conversationId", conversationRequest.getConversationId());
+        output.addProperty("from", conversationRequest.getSender());
+        JsonElement jsonMetadatas = JsonParser.parseString(conversationRequest.getMetadatas());
+        output.add("metadatas", jsonMetadatas);
+        output.addProperty("received", conversationRequest.getReceived());
+        output.addProperty("declined", conversationRequest.getDeclined());
+        return output;
+    }
+}
diff --git a/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/GsonFactory.java b/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/GsonFactory.java
index c295ae3c216658c2db688419e3073d3de2cc3c28..d157e3a30c8fbae20b93cbc9426f768e56eec2d2 100644
--- a/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/GsonFactory.java
+++ b/jams-common/src/main/java/net/jami/jams/common/serialization/adapters/GsonFactory.java
@@ -4,6 +4,8 @@ import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 
 import net.jami.jams.common.objects.contacts.Contact;
+import net.jami.jams.common.objects.conversations.Conversation;
+import net.jami.jams.common.objects.conversations.ConversationRequest;
 
 import org.bouncycastle.pkcs.PKCS10CertificationRequest;
 
@@ -14,6 +16,9 @@ public class GsonFactory {
     public static Gson createGson() {
         GsonBuilder gsonBuilder = new GsonBuilder();
         gsonBuilder.registerTypeAdapter(Contact.class, new ContactAdapter());
+        gsonBuilder.registerTypeAdapter(Conversation.class, new ConversationAdapter());
+        gsonBuilder.registerTypeAdapter(
+                ConversationRequest.class, new ConversationRequestAdapter());
         gsonBuilder.registerTypeAdapter(PKCS10CertificationRequest.class, new CSRDeserializer());
         gsonBuilder.registerTypeAdapter(PrivateKey.class, new PrivateKeyAdapter());
         gsonBuilder.registerTypeAdapter(X509Certificate.class, new X509CertificateAdapter());
diff --git a/jams-common/src/main/java/net/jami/jams/common/utils/ConversationMerger.java b/jams-common/src/main/java/net/jami/jams/common/utils/ConversationMerger.java
new file mode 100644
index 0000000000000000000000000000000000000000..fdb48edeca35673142793b896b686e64ef5751c5
--- /dev/null
+++ b/jams-common/src/main/java/net/jami/jams/common/utils/ConversationMerger.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.jami.jams.common.utils;
+
+import net.jami.jams.common.objects.conversations.Conversation;
+import net.jami.jams.common.objects.conversations.ConversationRequest;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class ConversationMerger {
+
+    public static List<Conversation> mergeConversations(
+            List<Conversation> remote, List<Conversation> local) {
+        List<Conversation> output = new ArrayList<>();
+        final HashMap<String, Conversation[]> conversationMap = new HashMap<>();
+        remote.forEach(
+                conversation -> {
+                    conversationMap.putIfAbsent(
+                            conversation.getId(), new Conversation[] {null, null});
+                    conversationMap.get(conversation.getId())[0] = conversation;
+                });
+        local.forEach(
+                conversation -> {
+                    conversationMap.putIfAbsent(
+                            conversation.getId(), new Conversation[] {null, null});
+                    conversationMap.get(conversation.getId())[1] = conversation;
+                });
+        conversationMap.forEach(
+                (k, v) -> {
+                    if (v[0] == null) output.add(v[1]);
+                    else if (v[1] == null) output.add(v[0]);
+                    else {
+                        // Merge policy we pick the conversation with the latest timestamp among
+                        // created and removed
+                        Conversation latestCreated =
+                                v[0].getCreated() > v[1].getCreated() ? v[0] : v[1];
+                        Conversation latestRemoved =
+                                v[0].getRemoved() > v[1].getRemoved() ? v[0] : v[1];
+                        if (latestCreated.getCreated() > latestRemoved.getRemoved()) {
+                            output.add(latestCreated);
+                        } else {
+                            output.add(latestRemoved);
+                        }
+                    }
+                });
+        return output;
+    }
+
+    public static List<ConversationRequest> mergeConversationRequests(
+            List<ConversationRequest> remote, List<ConversationRequest> local) {
+        List<ConversationRequest> output = new ArrayList<>();
+        final HashMap<String, ConversationRequest[]> conversationRequestMap = new HashMap<>();
+        remote.forEach(
+                conversationRequest -> {
+                    conversationRequestMap.putIfAbsent(
+                            conversationRequest.getConversationId(),
+                            new ConversationRequest[] {null, null});
+                    conversationRequestMap.get(conversationRequest.getConversationId())[0] =
+                            conversationRequest;
+                });
+        local.forEach(
+                conversationRequest -> {
+                    conversationRequestMap.putIfAbsent(
+                            conversationRequest.getConversationId(),
+                            new ConversationRequest[] {null, null});
+                    conversationRequestMap.get(conversationRequest.getConversationId())[1] =
+                            conversationRequest;
+                });
+        conversationRequestMap.forEach(
+                (k, v) -> {
+                    if (v[0] == null) output.add(v[1]);
+                    else if (v[1] == null) output.add(v[0]);
+                    else {
+                        // Merge policy we pick the conversation request with the latest timestamp
+                        // among
+                        // received and declined
+                        ConversationRequest latestReceived =
+                                v[0].getReceived() > v[1].getReceived() ? v[0] : v[1];
+                        ConversationRequest latestDeclined =
+                                v[0].getDeclined() > v[1].getDeclined() ? v[0] : v[1];
+                        if (latestReceived.getReceived() > latestDeclined.getDeclined()) {
+                            output.add(latestReceived);
+                        } else {
+                            output.add(latestDeclined);
+                        }
+                    }
+                });
+        return output;
+    }
+}
diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/contacts/ContactServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/contacts/ContactServlet.java
index 1fca80882a77c64ae4ffb3b62f60b380ab64fb61..7293c4b6a8e9ce47da5a53641dfada72f7b06b6d 100644
--- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/contacts/ContactServlet.java
+++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/contacts/ContactServlet.java
@@ -161,7 +161,7 @@ public class ContactServlet extends HttpServlet {
         remoteList.forEach(contact -> contact.setOwner(owner));
         List<Contact> result = ContactMerger.mergeContacts(localList, remoteList);
 
-        if (!dataStore.getContactDao().storeContactList(result))
+        if (result.size() > 0 && !dataStore.getContactDao().storeContactList(result))
             TomcatCustomErrorHandler.sendCustomError(resp, 500, "Could not store contacts!");
         else {
             resp.getOutputStream().write(gson.toJson(result).getBytes());
diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/conversations/ConversationRequestServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/conversations/ConversationRequestServlet.java
new file mode 100644
index 0000000000000000000000000000000000000000..ba54781d8166d82e85a162f47a3e98b94e53b377
--- /dev/null
+++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/conversations/ConversationRequestServlet.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package net.jami.jams.server.servlets.api.auth.conversations;
+
+import static net.jami.jams.server.Server.dataStore;
+
+import com.google.gson.Gson;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import net.jami.jams.common.objects.conversations.ConversationRequest;
+import net.jami.jams.common.serialization.adapters.GsonFactory;
+import net.jami.jams.common.serialization.tomcat.TomcatCustomErrorHandler;
+import net.jami.jams.common.utils.ConversationMerger;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+@WebServlet("/api/auth/conversationRequests")
+public class ConversationRequestServlet extends HttpServlet {
+    private static final Gson gson = GsonFactory.createGson();
+
+    /**
+     * @apiVersion 1.0.0
+     * @api {post} /api/auth/conversationRequests Sync conversationRequest list
+     * @apiName postConversationRequests
+     * @apiGroup ConversationRequests
+     *
+     * @apiParam {body} Conversation JSON representation of the conversationRequest object
+     * @apiParamExample {json} Request-Example: [
+     *                  {"conversationId":"1231221","received":1594742298377},
+     *                  {"conversationId":"213123","declined":1594742298377} ]
+     *
+     * @apiSuccess (200) {json} ConversationRequest[] user's converationRequests
+     * @apiError (500) {null} null contact could not be successfully added
+     */
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+            throws ServletException, IOException {
+        String owner = req.getAttribute("username").toString();
+        addConversationRequests(req, resp, owner);
+    }
+
+    public static void addConversationRequests(
+            HttpServletRequest req, HttpServletResponse resp, String owner) throws IOException {
+        List<ConversationRequest> localList =
+                dataStore.getConversationRequestDao().getByOwner(owner);
+        List<ConversationRequest> remoteList =
+                Arrays.asList(gson.fromJson(req.getReader(), ConversationRequest[].class));
+
+        remoteList.forEach(conversationRequest -> conversationRequest.setOwner(owner));
+        List<ConversationRequest> result =
+                ConversationMerger.mergeConversationRequests(localList, remoteList);
+
+        if (result.size() > 0
+                && !dataStore.getConversationRequestDao().storeConversationRequestList(result))
+            TomcatCustomErrorHandler.sendCustomError(
+                    resp, 500, "Could not store conversationRequests!");
+        else {
+            resp.getOutputStream().write(gson.toJson(result).getBytes());
+            resp.flushBuffer();
+        }
+    }
+}
diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/conversations/ConversationServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/conversations/ConversationServlet.java
new file mode 100644
index 0000000000000000000000000000000000000000..11dae5e5dbf0b2e05e5a8b3e92604f8058b46ce9
--- /dev/null
+++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/conversations/ConversationServlet.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 by Savoir-faire Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package net.jami.jams.server.servlets.api.auth.conversations;
+
+import static net.jami.jams.server.Server.dataStore;
+
+import com.google.gson.Gson;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import net.jami.jams.common.objects.conversations.Conversation;
+import net.jami.jams.common.serialization.adapters.GsonFactory;
+import net.jami.jams.common.serialization.tomcat.TomcatCustomErrorHandler;
+import net.jami.jams.common.utils.ConversationMerger;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+@WebServlet("/api/auth/conversations")
+public class ConversationServlet extends HttpServlet {
+    private static final Gson gson = GsonFactory.createGson();
+
+    /**
+     * @apiVersion 1.0.0
+     * @api {post} /api/auth/conversations Sync conversation list
+     * @apiName postConversations
+     * @apiGroup Conversations
+     *
+     * @apiParam {body} Conversation JSON representation of the conversation object
+     * @apiParamExample {json} Request-Example: [
+     *                  {"id":"1231221","created":1594742298377},
+     *                  {"id":"213123","removed":1594742298377} ]
+     *
+     * @apiSuccess (200) {json} Conversation[] user's conversations
+     * @apiError (500) {null} null conversation could not be successfully synced
+     */
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+            throws ServletException, IOException {
+        String owner = req.getAttribute("username").toString();
+        addConversations(req, resp, owner);
+    }
+
+    public static void addConversations(
+            HttpServletRequest req, HttpServletResponse resp, String owner) throws IOException {
+        List<Conversation> remoteList =
+                Arrays.asList(gson.fromJson(req.getReader(), Conversation[].class));
+        List<Conversation> localList = dataStore.getConversationDao().getByOwner(owner);
+        remoteList.forEach(conversation -> conversation.setOwner(owner));
+        List<Conversation> result = ConversationMerger.mergeConversations(localList, remoteList);
+
+        if (result.size() > 0 && !dataStore.getConversationDao().storeConversationList(result))
+            TomcatCustomErrorHandler.sendCustomError(resp, 500, "Could not store conversations!");
+        else {
+            resp.getOutputStream().write(gson.toJson(result).getBytes());
+            resp.flushBuffer();
+        }
+    }
+}
diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/filters/ARequestLoggingFilter.java b/jams-server/src/main/java/net/jami/jams/server/servlets/filters/ARequestLoggingFilter.java
index 412bedc66dc1155e8c2834363999cda037d5983e..1f97599876e15e58102c54c00963e1b0439827dd 100644
--- a/jams-server/src/main/java/net/jami/jams/server/servlets/filters/ARequestLoggingFilter.java
+++ b/jams-server/src/main/java/net/jami/jams/server/servlets/filters/ARequestLoggingFilter.java
@@ -19,7 +19,11 @@ public class ARequestLoggingFilter implements Filter {
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
             throws IOException, ServletException {
         HttpServletRequest req = ((HttpServletRequest) request);
-        log.info("Request: {} {}", req.getMethod(), req.getRequestURI());
+        log.info(
+                "Request: {} {} - Remote IP: {}",
+                req.getMethod(),
+                req.getRequestURI(),
+                request.getRemoteAddr());
         chain.doFilter(request, response);
     }
 }
diff --git a/ldap-connector/src/main/java/net/jami/jams/ldap/connector/service/AuthenticationService.java b/ldap-connector/src/main/java/net/jami/jams/ldap/connector/service/AuthenticationService.java
index 7280fdccf002c09a78dbaf5aa3c6700253cbe109..a697bd42bd80e6db90b0c340e1cbe85e8c4b91c3 100644
--- a/ldap-connector/src/main/java/net/jami/jams/ldap/connector/service/AuthenticationService.java
+++ b/ldap-connector/src/main/java/net/jami/jams/ldap/connector/service/AuthenticationService.java
@@ -46,7 +46,8 @@ public class AuthenticationService {
     public boolean authenticateUser(String username, String password) {
         try {
             FormatDnResolver dnResolver = new FormatDnResolver();
-            dnResolver.setFormat(settings.getUsernameField() + "=%s," + settings.getBaseDN());
+            dnResolver.setFormat(
+                    settings.getUsernameField() + "=%s,ou=users," + settings.getBaseDN());
             SimpleBindAuthenticationHandler bindAuthenticationHandler =
                     new SimpleBindAuthenticationHandler(connectionFactory);
             Authenticator auth = new Authenticator();