diff --git a/Makefile b/Makefile
index d8f59f5f364a6b6a7dceec3c37cd58119f6be4f9..e4e786be648fa4c18f87476390fb3b82729a7802 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,10 @@
 SHELL = /bin/bash
 pre-setup:;
 	@echo "Creating environment file from template"
-	@rm -f .env && envsubst < config/.env.sample > .env
+	@rm -f .env && envsubst < config/.env.sample | tr -d '\r' > .env
 setup:;
 	@echo "Generating secret files from templates using environment file + variables"
-	@source .env && rm -f ./config/application/secret.properties && envsubst < config/application/secret.properties.sample > config/application/secret.properties
+	@source .env && rm -f ./config/application/secret.properties && envsubst < config/application/secret.properties.sample | tr -d '\r' > config/application/secret.properties
 dev-start:;
 	source .env && mvn compile quarkus:dev
 clean:;
diff --git a/docker-compose.gitlab.yaml b/docker-compose.gitlab.yaml
index c87fd09e75c3bf5be825191e3ddd56e7bb4c041d..fbba762cb6b138e2e191b2e4a483c296985f3ba4 100644
--- a/docker-compose.gitlab.yaml
+++ b/docker-compose.gitlab.yaml
@@ -1,8 +1,8 @@
 version: '3'
 services:
-  web:
+  gitlab:
     container_name: gitlab
-    image: 'gitlab/gitlab-ce:latest'
+    image: 'gitlab/gitlab-ee:14.7.7-ee.0'
     restart: always
     environment:
       VIRTUAL_HOST: "gitlab.dev.docker"
@@ -19,4 +19,24 @@ services:
       - '/localdocker/gitlab/config:/etc/gitlab'
       - '/localdocker/gitlab/logs:/var/log/gitlab'
       - '/localdocker/gitlab/data:/var/opt/gitlab'
+  application:
+    build:
+      dockerfile: ./src/main/docker/Dockerfile.jvm
+    ports:
+      - 8090:8080
+    environment:
+      - CONFIG_SECRET_PATH=/var/run/secrets/secret.properties
+    volumes:
+      - $CONFIG_SECRET_PATH:/var/run/secrets/secret.properties
+    deploy:
+      restart_policy:
+        condition: on-failure
+        max_attempts: 5
+      resources:
+        limits:
+          cpus: '0.5'
+          memory: 256M
+        reservations:
+          cpus: '0.001'
+          memory: 192M
 
diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/WebhookBody.java b/src/main/java/org/eclipsefoundation/git/eca/model/WebhookBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..d898fa7c34fb19caa197efc4c87b4fabbdad98b4
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/model/WebhookBody.java
@@ -0,0 +1,50 @@
+package org.eclipsefoundation.git.eca.model;
+
+import java.util.Date;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.auto.value.AutoValue;
+
+public abstract class WebhookBody {
+    public abstract Date getCreatedAt();
+    public abstract Date getUpdatedAt();
+    public abstract String getEventName();
+    public abstract String getUserEmail();
+    public abstract String getUserName();
+    public abstract String getUserUsername();
+    public abstract int getUserId();
+
+    public static abstract class Builder<T extends Builder<T>>  {
+        public abstract T setCreatedAt(Date createdAt);
+        public abstract T setUpdatedAt(Date updatedAt);
+        public abstract T setEventName(String eventName);
+        public abstract T setUserEmail(String userEmail);
+        public abstract T setUserName(String userName);
+        public abstract T setUserUsername(String userUsername);
+        public abstract T setUserId(int userId);
+    }
+
+    @AutoValue
+    @JsonDeserialize(builder = AutoValue_WebhookBody_GroupAccessWebhook.Builder.class)
+    public static abstract class GroupAccessWebhook extends WebhookBody {
+        public abstract String getGroupName();
+        public abstract String getGroupPath();
+        public abstract String getGroupAccess();
+        public abstract int getGroupId();
+
+        public static Builder builder() {
+            return new AutoValue_WebhookBody_GroupAccessWebhook.Builder();
+        }
+
+        @AutoValue.Builder
+        @JsonPOJOBuilder(withPrefix = "set")
+        public static abstract class Builder extends WebhookBody.Builder<Builder> {
+            public abstract Builder setGroupName(String groupName);
+            public abstract Builder setGroupPath(String groupPath);
+            public abstract Builder setGroupAccess(String groupAccess);
+            public abstract Builder setGroupId(int groupId);
+            public abstract GroupAccessWebhook build();
+        }
+    }
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/WebhookResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/WebhookResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..06ac65dbfc671d96bdc6b9d1a3e9f768310cc7d2
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/resource/WebhookResource.java
@@ -0,0 +1,28 @@
+package org.eclipsefoundation.git.eca.resource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Path("webhooks")
+public class WebhookResource {
+    private static final Logger LOGGER = LoggerFactory.getLogger(WebhookResource.class);
+
+    @GET
+    @Path("group_access")
+    public Response ok(){
+        return Response.ok().build();
+    }
+
+    @POST
+    @Path("group_access")
+    public Response ok(@HeaderParam("X-Gitlab-Event") String eventType, String json) {
+        LOGGER.error("Event type: {}, body: {}", eventType, json);
+        return Response.ok().build();
+    }
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/EventProcessingService.java b/src/main/java/org/eclipsefoundation/git/eca/service/EventProcessingService.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c4ff20721f1e06d2d36ac04c41204567c87711e
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/EventProcessingService.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2022 Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.git.eca.service;
+
+/**
+ * Interface for service that processes webhook events from Gitlab.
+ */
+public interface EventProcessingService {
+
+    void processEvent(String eventType, String body);
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/AsyncEventProcessingService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/AsyncEventProcessingService.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5e8fca774134ab0aa1972d6680efe0a63ece2c1
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/AsyncEventProcessingService.java
@@ -0,0 +1,57 @@
+package org.eclipsefoundation.git.eca.service.impl;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+
+import org.eclipsefoundation.git.eca.model.WebhookBody.GroupAccessWebhook;
+import org.eclipsefoundation.git.eca.service.EventProcessingService;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Default asynchronous event pool for handling gitlab events.
+ */
+@ApplicationScoped
+public class AsyncEventProcessingService implements EventProcessingService {
+
+    @Inject
+    ObjectMapper objectMapper;
+
+    private ExecutorService pool;
+
+    @PostConstruct
+    void init() {
+        // create a fixed thread pool to queue events that come in
+        this.pool = Executors.newFixedThreadPool(5);
+    }
+
+    @Override
+    public void processEvent(String eventType, String body) {
+        this.pool.execute(() -> {
+            switch (eventType) {
+                case "user_add_to_group":
+                case "user_update_for_group":
+                    processGroupMembershipChange(null);
+                    break;
+                case "user_add_to_team":
+                case "user_update_for_team":
+                    processTeamMembershipChange(null);
+                    break;
+                default:
+            }
+        });
+    }
+
+    private void processGroupMembershipChange(GroupAccessWebhook b) {
+
+    }
+
+    private void processTeamMembershipChange(GroupAccessWebhook b) {
+
+    }
+    
+}