diff --git a/src/main/java/sh/libre/scim/core/GroupScimClient.java b/src/main/java/sh/libre/scim/core/GroupScimClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..1483d3bda3224292a63889f3971ef2f666623ef6
--- /dev/null
+++ b/src/main/java/sh/libre/scim/core/GroupScimClient.java
@@ -0,0 +1,37 @@
+package sh.libre.scim.core;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.storage.user.SynchronizationResult;
+
+public class GroupScimClient implements ScimClientInterface<GroupModel> {
+    public static GroupScimClient newGroupScimClient(ComponentModel scimServer, KeycloakSession session) {
+        return new GroupScimClient();
+    }
+
+    @Override
+    public void create(GroupModel resource) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void replace(GroupModel resource) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void delete(String id) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void sync(SynchronizationResult result) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void close() throws Exception {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/main/java/sh/libre/scim/core/ScimClientInterface.java b/src/main/java/sh/libre/scim/core/ScimClientInterface.java
new file mode 100644
index 0000000000000000000000000000000000000000..1affb82c71e32d027d89d533b77bc033bb839271
--- /dev/null
+++ b/src/main/java/sh/libre/scim/core/ScimClientInterface.java
@@ -0,0 +1,42 @@
+package sh.libre.scim.core;
+
+import org.keycloak.models.RoleMapperModel;
+import org.keycloak.storage.user.SynchronizationResult;
+
+/**
+ * An interface for defining ScimClient.
+ * A ScimClient provides methods for propagating CRUD and sync of a dedicated SCIM Resource (e.g. {@link de.captaingoldfish.scim.sdk.common.resources.User}). to a SCIM server defined in a {@link sh.libre.scim.storage.ScimStorageProvider}.
+ *
+ * @param <M> the keycloack model to synchronize (e.g. UserModel or GroupModel)
+ */
+public interface ScimClientInterface<M extends RoleMapperModel> extends AutoCloseable {
+
+    /**
+     * Propagates the creation of the given keycloack model to a SCIM server.
+     *
+     * @param resource the created resource to propagate (e.g. a new UserModel)
+     */
+    // TODO rename method (e.g. propagateCreation)
+    void create(M resource);
+
+    /**
+     * Propagates the update of the given keycloack model to a SCIM server.
+     *
+     * @param resource the resource creation to propagate (e.g. a UserModel)
+     */
+    void replace(M resource);
+
+    /**
+     * Propagates the deletion of an element to a SCIM server.
+     *
+     * @param id the deleted resource's id to propagate (e.g. id of a UserModel)
+     */
+    void delete(String id);
+
+    /**
+     * Synchronizes resources between SCIM server and keycloack, according to configuration.
+     *
+     * @param result the synchronization result to update for indicating triggered operations (e.g. user deletions)
+     */
+    void sync(SynchronizationResult result);
+}
diff --git a/src/main/java/sh/libre/scim/core/ScimDispatcher.java b/src/main/java/sh/libre/scim/core/ScimDispatcher.java
index df4e220a3d09f5247046495a1267a125a86fa9b6..dcbe886afe5207a9d10c56d5bba012d5484d19f1 100644
--- a/src/main/java/sh/libre/scim/core/ScimDispatcher.java
+++ b/src/main/java/sh/libre/scim/core/ScimDispatcher.java
@@ -6,13 +6,13 @@ import org.keycloak.models.KeycloakSession;
 import sh.libre.scim.storage.ScimStorageProviderFactory;
 
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 
+/**
+ * In charge of sending SCIM Request to all registered SCIM servers.
+ */
 public class ScimDispatcher {
 
-    public static final String SCOPE_USER = "user";
-
-    public static final String SCOPE_GROUP = "group";
-
     private static final Logger LOGGER = Logger.getLogger(ScimDispatcher.class);
 
     private final KeycloakSession session;
@@ -21,22 +21,45 @@ public class ScimDispatcher {
         this.session = session;
     }
 
-    public void run(String scope, Consumer<ScimClient> f) {
-        session.getContext().getRealm().getComponentsStream()
-                .filter((m) -> {
-                    return ScimStorageProviderFactory.ID.equals(m.getProviderId())
-                            && m.get("enabled", true)
-                            && m.get("propagation-" + scope, false);
-                })
-                .forEach(m -> runOne(m, f));
+    public void dispatchUserModificationToAll(Consumer<UserScimClient> operationToDispatch) {
+        getAllSCIMServer(Scope.USER).forEach(scimServer -> dispatchUserModificationToOne(scimServer, operationToDispatch));
     }
 
-    public void runOne(ComponentModel m, Consumer<ScimClient> f) {
-        LOGGER.infof("%s %s %s %s", m.getId(), m.getName(), m.getProviderId(), m.getProviderType());
-        try (ScimClient client = ScimClient.newScimClient(m, session)) {
-            f.accept(client);
+    public void dispatchUserModificationToOne(ComponentModel scimServer, Consumer<UserScimClient> operationToDispatch) {
+        LOGGER.infof("%s %s %s %s", scimServer.getId(), scimServer.getName(), scimServer.getProviderId(), scimServer.getProviderType());
+        try (UserScimClient client = UserScimClient.newUserScimClient(scimServer, session)) {
+            operationToDispatch.accept(client);
         } catch (Exception e) {
             LOGGER.error(e);
         }
     }
+
+    public void dispatchGroupModificationToAll(Consumer<GroupScimClient> operationToDispatch) {
+        getAllSCIMServer(Scope.GROUP).forEach(scimServer -> dispatchGroupModificationToOne(scimServer, operationToDispatch));
+    }
+
+    public void dispatchGroupModificationToOne(ComponentModel scimServer, Consumer<GroupScimClient> operationToDispatch) {
+        LOGGER.infof("%s %s %s %s", scimServer.getId(), scimServer.getName(), scimServer.getProviderId(), scimServer.getProviderType());
+        try (GroupScimClient client = GroupScimClient.newGroupScimClient(scimServer, session)) {
+            operationToDispatch.accept(client);
+        } catch (Exception e) {
+            LOGGER.error(e);
+        }
+    }
+
+    /**
+     * @param scope The {@link Scope} to consider (User or Group)
+     * @return all enabled registered SCIM Servers with propagation enabled for the given scope
+     */
+    private Stream<ComponentModel> getAllSCIMServer(Scope scope) {
+        // TODO : we could initiative this list once and invalidate it when configuration changes
+        return session.getContext().getRealm().getComponentsStream()
+                .filter(m -> ScimStorageProviderFactory.ID.equals(m.getProviderId())
+                             && m.get("enabled", true)
+                             && m.get("propagation-" + scope.name(), false));
+    }
+
+    public enum Scope {
+        USER, GROUP
+    }
 }
diff --git a/src/main/java/sh/libre/scim/core/UserScimClient.java b/src/main/java/sh/libre/scim/core/UserScimClient.java
index 789d852d6811143c658429f9e051a7b3da36ba66..4f257a0fca661b6f027f4d6c24fb5f99f9a97cf5 100644
--- a/src/main/java/sh/libre/scim/core/UserScimClient.java
+++ b/src/main/java/sh/libre/scim/core/UserScimClient.java
@@ -26,7 +26,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
-public class UserScimClient implements AutoCloseable {
+public class UserScimClient implements ScimClientInterface<UserModel> {
 
     private static final Logger LOGGER = Logger.getLogger(UserScimClient.class);
 
@@ -86,6 +86,7 @@ public class UserScimClient implements AutoCloseable {
         return new UserScimClient(scimRequestBuilder, retryRegistry, session, scimProviderConfiguration);
     }
 
+    @Override
     public void create(UserModel userModel) {
         UserAdapter adapter = new UserAdapter(this.keycloakSession, this.scimProviderConfiguration.getId());
         adapter.apply(userModel);
@@ -116,6 +117,7 @@ public class UserScimClient implements AutoCloseable {
         adapter.saveMapping();
     }
 
+    @Override
     public void replace(UserModel userModel) {
         UserAdapter adapter = new UserAdapter(this.keycloakSession, this.scimProviderConfiguration.getId());
         try {
@@ -146,6 +148,7 @@ public class UserScimClient implements AutoCloseable {
         }
     }
 
+    @Override
     public void delete(String id) {
         UserAdapter adapter = new UserAdapter(this.keycloakSession, this.scimProviderConfiguration.getId());
         adapter.setId(id);
@@ -262,6 +265,7 @@ public class UserScimClient implements AutoCloseable {
         }
     }
 
+    @Override
     public void sync(SynchronizationResult syncRes) {
         if (this.scimProviderConfiguration.isSyncImport()) {
             this.importResources(syncRes);
diff --git a/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java b/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java
index a36dc574077baf5438411690a0487856615f29f6..737e0819d4c85cc3f53c7aae600eb1dddb4b5718 100644
--- a/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java
+++ b/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java
@@ -10,11 +10,8 @@ import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.UserModel;
-import sh.libre.scim.core.GroupAdapter;
 import sh.libre.scim.core.ScimDispatcher;
-import sh.libre.scim.core.UserAdapter;
 
-import java.util.HashMap;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -50,14 +47,13 @@ public class ScimEventListenerProvider implements EventListenerProvider {
         switch (eventType) {
             case REGISTER -> {
                 UserModel user = getUser(eventUserId);
-                dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.create(UserAdapter.class, user));
+                dispatcher.dispatchUserModificationToAll(client -> client.create(user));
             }
             case UPDATE_EMAIL, UPDATE_PROFILE -> {
                 UserModel user = getUser(eventUserId);
-                dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.replace(UserAdapter.class, user));
+                dispatcher.dispatchUserModificationToAll(client -> client.replace(user));
             }
-            case DELETE_ACCOUNT ->
-                    dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.delete(UserAdapter.class, eventUserId));
+            case DELETE_ACCOUNT -> dispatcher.dispatchUserModificationToAll(client -> client.delete(eventUserId));
             default -> LOGGER.trace("ignore event " + eventType);
         }
     }
@@ -77,17 +73,16 @@ public class ScimEventListenerProvider implements EventListenerProvider {
                 switch (event.getOperationType()) {
                     case CREATE -> {
                         UserModel user = getUser(userId);
-                        dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.create(UserAdapter.class, user));
+                        dispatcher.dispatchUserModificationToAll(client -> client.create(user));
                         user.getGroupsStream().forEach(group -> {
-                            dispatcher.run(ScimDispatcher.SCOPE_GROUP, (client) -> client.replace(GroupAdapter.class, group));
+                            dispatcher.dispatchGroupModificationToAll(client -> client.replace(group));
                         });
                     }
                     case UPDATE -> {
                         UserModel user = getUser(userId);
-                        dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.replace(UserAdapter.class, user));
+                        dispatcher.dispatchUserModificationToAll(client -> client.replace(user));
                     }
-                    case DELETE ->
-                            dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.delete(UserAdapter.class, userId));
+                    case DELETE -> dispatcher.dispatchUserModificationToAll(client -> client.delete(userId));
                 }
             }
             case GROUP -> {
@@ -96,14 +91,13 @@ public class ScimEventListenerProvider implements EventListenerProvider {
                 switch (event.getOperationType()) {
                     case CREATE -> {
                         GroupModel group = getGroup(groupId);
-                        dispatcher.run(ScimDispatcher.SCOPE_GROUP, (client) -> client.create(GroupAdapter.class, group));
+                        dispatcher.dispatchGroupModificationToAll(client -> client.create(group));
                     }
                     case UPDATE -> {
                         GroupModel group = getGroup(groupId);
-                        dispatcher.run(ScimDispatcher.SCOPE_GROUP, (client) -> client.replace(GroupAdapter.class, group));
+                        dispatcher.dispatchGroupModificationToAll(client -> client.replace(group));
                     }
-                    case DELETE -> dispatcher.run(ScimDispatcher.SCOPE_GROUP,
-                            (client) -> client.delete(GroupAdapter.class, groupId));
+                    case DELETE -> dispatcher.dispatchGroupModificationToAll(client -> client.delete(groupId));
                 }
             }
             case GROUP_MEMBERSHIP -> {
@@ -113,7 +107,7 @@ public class ScimEventListenerProvider implements EventListenerProvider {
                 GroupModel group = getGroup(groupId);
                 group.setSingleAttribute("scim-dirty", BooleanUtils.TRUE);
                 UserModel user = getUser(userId);
-                dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.replace(UserAdapter.class, user));
+                dispatcher.dispatchUserModificationToAll(client -> client.replace(user));
             }
             case REALM_ROLE_MAPPING -> {
                 String type = matcher.group(1);
@@ -122,12 +116,12 @@ public class ScimEventListenerProvider implements EventListenerProvider {
                 switch (type) {
                     case "users" -> {
                         UserModel user = getUser(id);
-                        dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.replace(UserAdapter.class, user));
+                        dispatcher.dispatchUserModificationToAll(client -> client.replace(user));
                     }
                     case "groups" -> {
                         GroupModel group = getGroup(id);
                         session.users().getGroupMembersStream(session.getContext().getRealm(), group).forEach(user -> {
-                            dispatcher.run(ScimDispatcher.SCOPE_USER, (client) -> client.replace(UserAdapter.class, user));
+                            dispatcher.dispatchUserModificationToAll(client -> client.replace(user));
                         });
                     }
                 }
diff --git a/src/main/java/sh/libre/scim/storage/ScimStorageProviderFactory.java b/src/main/java/sh/libre/scim/storage/ScimStorageProviderFactory.java
index f805cd65e7c08e4563d46eb9c24071a6edd90d94..e5f4b36ee750e8e76aea951cac95f6ff98b5ac44 100644
--- a/src/main/java/sh/libre/scim/storage/ScimStorageProviderFactory.java
+++ b/src/main/java/sh/libre/scim/storage/ScimStorageProviderFactory.java
@@ -137,10 +137,10 @@ public class ScimStorageProviderFactory
                 session.getContext().setRealm(realm);
                 ScimDispatcher dispatcher = new ScimDispatcher(session);
                 if (BooleanUtils.TRUE.equals(model.get("propagation-user"))) {
-                    dispatcher.runOne(model, (client) -> client.sync(UserAdapter.class, result));
+                    dispatcher.dispatchUserModificationToOne(model, client -> client.sync(result));
                 }
                 if (BooleanUtils.TRUE.equals(model.get("propagation-group"))) {
-                    dispatcher.runOne(model, (client) -> client.sync(GroupAdapter.class, result));
+                    dispatcher.dispatchGroupModificationToOne(model, client -> client.sync(result));
                 }
             }
 
@@ -169,8 +169,7 @@ public class ScimStorageProviderFactory
                         for (GroupModel group : session.groups().getGroupsStream(realm)
                                 .filter(x -> BooleanUtils.TRUE.equals(x.getFirstAttribute("scim-dirty"))).toList()) {
                             LOGGER.debug(group.getName() + " is dirty");
-                            dispatcher.run(ScimDispatcher.SCOPE_GROUP,
-                                    (client) -> client.replace(GroupAdapter.class, group));
+                            dispatcher.dispatchGroupModificationToAll(client -> client.replace(group));
                             group.removeAttribute("scim-dirty");
                         }
                     }