diff --git a/.gitignore b/.gitignore
index 51fe9e44da7aeec20cfdc695b9fab2a8e527d82f..3b2dca2c06ce55d0d6eb697c54343365960639f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,7 @@ release.properties
 secret/
 secrets/
 secret.properties
+*.pem
 
 # Additional build resources
 src/test/resources/schemas
diff --git a/pom.xml b/pom.xml
index 216db0fa79705bb98311e31e0d37c04c86cc0cf8..3baf3ec64be211e7a3a15756b679c1036a30b24e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,6 +65,21 @@
       <groupId>io.quarkus</groupId>
       <artifactId>quarkus-smallrye-context-propagation</artifactId>
     </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-smallrye-jwt</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcprov-jdk15on</artifactId>
+    </dependency>
+    <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->
+    <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcpkix-jdk15on</artifactId>
+      <version>1.70</version>
+    </dependency>
+
     <dependency>
       <groupId>io.quarkus</groupId>
       <artifactId>quarkus-rest-client</artifactId>
diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/GithubAPI.java b/src/main/java/org/eclipsefoundation/git/eca/api/GithubAPI.java
new file mode 100644
index 0000000000000000000000000000000000000000..d68e938cc0e4b6a783ccaaa86fc9a3da8b37c7bf
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/api/GithubAPI.java
@@ -0,0 +1,50 @@
+/**
+ * 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.api;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.eclipsefoundation.git.eca.api.models.GithubAccessToken;
+import org.eclipsefoundation.git.eca.api.models.GithubCommitStatusRequest;
+
+/**
+ * Bindings used by the webhook logic to retrieve data about the PR to be validated.
+ * 
+ * @author Martin Lowe
+ *
+ */
+@RegisterRestClient(baseUri = "https://api.github.com")
+@Produces("application/json")
+public interface GithubAPI {
+
+    @GET
+    @Path("repos/{repoFull}/pulls/{pullNumber}/commits")
+    public Response getCommits(@HeaderParam("authorization") String bearer, @HeaderParam("X-GitHub-Api-Version") String apiVersion,
+            @PathParam("repoFull") String repoFull, @PathParam("pullNumber") int pullNumber);
+
+    @POST
+    @Path("repos/{repoFull}/statuses/{prHeadSha}")
+    public Response updateStatus(@HeaderParam("authorization") String bearer, @HeaderParam("X-GitHub-Api-Version") String apiVersion,
+            @PathParam("repoFull") String repoFull, @PathParam("prHeadSha") String prHeadSha, GithubCommitStatusRequest commitStatusUpdate);
+
+    @POST
+    @Path("app/installations/{installationId}/access_tokens")
+    public GithubAccessToken getNewAccessToken(@HeaderParam("authorization") String bearer,
+            @HeaderParam("X-GitHub-Api-Version") String apiVersion, @PathParam("installationId") String installationId);
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubAccessToken.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubAccessToken.java
new file mode 100644
index 0000000000000000000000000000000000000000..437e4a6c19466e7864c22203a7a6db409c4602e1
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubAccessToken.java
@@ -0,0 +1,43 @@
+/**
+ * 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.api.models;
+
+import java.time.LocalDateTime;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.auto.value.AutoValue;
+
+/**
+ * @author martin
+ *
+ */
+@AutoValue
+@JsonDeserialize(builder = AutoValue_GithubAccessToken.Builder.class)
+public abstract class GithubAccessToken {
+
+    public abstract String getToken();
+    public abstract LocalDateTime getExpiresAt();
+    
+    public static Builder builder() {
+        return new AutoValue_GithubAccessToken.Builder();
+    }
+
+    @AutoValue.Builder
+    @JsonPOJOBuilder(withPrefix = "set")
+    public abstract static class Builder {
+        public abstract Builder setToken(String token);
+        public abstract Builder setExpiresAt(LocalDateTime expiresAt);
+
+        public abstract GithubAccessToken build();
+    }
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommit.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommit.java
new file mode 100644
index 0000000000000000000000000000000000000000..811c9d14da69f7d9cdbaba36e45fa16218d964d5
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommit.java
@@ -0,0 +1,79 @@
+/**
+ * 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.api.models;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.auto.value.AutoValue;
+
+/**
+ * @author Martin Lowe
+ *
+ */
+@AutoValue
+@JsonDeserialize(builder = AutoValue_GithubCommit.Builder.class)
+public abstract class GithubCommit {
+    public abstract String getSha();
+    public abstract CommitData getCommit();
+
+    public static Builder builder() {
+        return new AutoValue_GithubCommit.Builder();
+    }
+
+    @AutoValue.Builder
+    @JsonPOJOBuilder(withPrefix = "set")
+    public abstract static class Builder {
+        public abstract Builder setSha(String sha);
+        public abstract Builder setCommit(CommitData commit);
+
+        public abstract GithubCommit build();
+    }
+
+    @AutoValue
+    @JsonDeserialize(builder = AutoValue_GithubCommit_CommitData.Builder.class)
+    public abstract static class CommitData {
+        public abstract CommitUser getAuthor();
+        public abstract CommitUser getCommitter();
+
+        public static Builder builder() {
+            return new AutoValue_GithubCommit_CommitData.Builder();
+        }
+
+        @AutoValue.Builder
+        @JsonPOJOBuilder(withPrefix = "set")
+        public abstract static class Builder {
+            public abstract Builder setAuthor(CommitUser author);
+            public abstract Builder setCommitter(CommitUser committer);
+
+            public abstract CommitData build();
+        }
+    }
+    @AutoValue
+    @JsonDeserialize(builder = AutoValue_GithubCommit_CommitUser.Builder.class)
+    public abstract static class CommitUser {
+        public abstract String getName();
+        public abstract String getEmail();
+
+        public static Builder builder() {
+            return new AutoValue_GithubCommit_CommitUser.Builder();
+        }
+
+        @AutoValue.Builder
+        @JsonPOJOBuilder(withPrefix = "set")
+        public abstract static class Builder {
+            public abstract Builder setName(String name);
+            public abstract Builder setEmail(String email);
+
+            public abstract CommitUser build();
+        }
+    }
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommitStatusRequest.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommitStatusRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..53522c1f9d33c8be02ac06d7fb938eeeefe02b2f
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommitStatusRequest.java
@@ -0,0 +1,44 @@
+/**
+ * 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.api.models;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.auto.value.AutoValue;
+
+/**
+ * @author martin
+ *
+ */
+@AutoValue
+@JsonDeserialize(builder = AutoValue_GithubCommitStatusRequest.Builder.class)
+public abstract class GithubCommitStatusRequest {
+    public abstract String getState();
+    public abstract String getTargetUrl();
+    public abstract String getDescription();
+    public abstract String getContext();
+
+    public static Builder builder() {
+        return new AutoValue_GithubCommitStatusRequest.Builder();
+    }
+
+    @AutoValue.Builder
+    @JsonPOJOBuilder(withPrefix = "set")
+    public abstract static class Builder {
+        public abstract Builder setState(String state);
+        public abstract Builder setTargetUrl(String targetUrl);
+        public abstract Builder setDescription(String description);
+        public abstract Builder setContext(String context);
+
+        public abstract GithubCommitStatusRequest build();
+    }
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubWebhookRequest.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubWebhookRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c0e93ac18311c64aae0b083257276b75d13cc5d
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubWebhookRequest.java
@@ -0,0 +1,134 @@
+/**
+ * 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.api.models;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.auto.value.AutoValue;
+
+/**
+ * Represents incoming webhook requests from the Git ECA app installations on Github.
+ * 
+ * @author Martin Lowe
+ *
+ */
+@AutoValue
+@JsonDeserialize(builder = AutoValue_GithubWebhookRequest.Builder.class)
+public abstract class GithubWebhookRequest {
+
+    public abstract String getAction();
+
+    public abstract Installation getInstallation();
+
+    public abstract Repository getRepository();
+
+    public abstract PullRequest getPullRequest();
+
+    public static Builder builder() {
+        return new AutoValue_GithubWebhookRequest.Builder();
+    }
+
+    @AutoValue.Builder
+    @JsonPOJOBuilder(withPrefix = "set")
+    public abstract static class Builder {
+        public abstract Builder setAction(String action);
+
+        public abstract Builder setInstallation(Installation installation);
+
+        public abstract Builder setRepository(Repository repository);
+
+        public abstract Builder setPullRequest(PullRequest pullRequest);
+
+        public abstract GithubWebhookRequest build();
+    }
+
+    @AutoValue
+    @JsonDeserialize(builder = AutoValue_GithubWebhookRequest_Repository.Builder.class)
+    public abstract static class Repository {
+
+        public abstract String getFullName();
+
+        public abstract String getHtmlUrl();
+
+        public static Builder builder() {
+            return new AutoValue_GithubWebhookRequest_Repository.Builder();
+        }
+
+        @AutoValue.Builder
+        @JsonPOJOBuilder(withPrefix = "set")
+        public abstract static class Builder {
+            public abstract Builder setFullName(String fullName);
+
+            public abstract Builder setHtmlUrl(String htmlUrl);
+
+            public abstract Repository build();
+        }
+    }
+
+    @AutoValue
+    @JsonDeserialize(builder = AutoValue_GithubWebhookRequest_PullRequest.Builder.class)
+    public abstract static class PullRequest {
+
+        public abstract Integer getNumber();
+        public abstract PullRequestHead getHead();
+
+        public static Builder builder() {
+            return new AutoValue_GithubWebhookRequest_PullRequest.Builder();
+        }
+
+        @AutoValue.Builder
+        @JsonPOJOBuilder(withPrefix = "set")
+        public abstract static class Builder {
+            public abstract Builder setNumber(Integer number);
+            public abstract Builder setHead(PullRequestHead head);
+
+            public abstract PullRequest build();
+        }
+    }
+    
+    @AutoValue
+    @JsonDeserialize(builder = AutoValue_GithubWebhookRequest_PullRequestHead.Builder.class)
+    public abstract static class PullRequestHead {
+
+        public abstract String getSha();
+
+        public static Builder builder() {
+            return new AutoValue_GithubWebhookRequest_PullRequestHead.Builder();
+        }
+
+        @AutoValue.Builder
+        @JsonPOJOBuilder(withPrefix = "set")
+        public abstract static class Builder {
+            public abstract Builder setSha(String sha);
+
+            public abstract PullRequestHead build();
+        }
+    }
+
+    @AutoValue
+    @JsonDeserialize(builder = AutoValue_GithubWebhookRequest_Installation.Builder.class)
+    public abstract static class Installation {
+        public abstract String getId();
+
+        public static Builder builder() {
+            return new AutoValue_GithubWebhookRequest_Installation.Builder();
+        }
+
+        @AutoValue.Builder
+        @JsonPOJOBuilder(withPrefix = "set")
+        public abstract static class Builder {
+            public abstract Builder setId(String id);
+
+            public abstract Installation build();
+        }
+    }
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/InterestGroupData.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java
similarity index 92%
rename from src/main/java/org/eclipsefoundation/git/eca/model/InterestGroupData.java
rename to src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java
index e2fd06bd8cf50dc3194849403206a0b00885cfb2..bea777d515c2554cae4c922221e9bd3c8910a5d8 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/model/InterestGroupData.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java
@@ -1,9 +1,9 @@
-package org.eclipsefoundation.git.eca.model;
+package org.eclipsefoundation.git.eca.api.models;
 
 import java.util.List;
 
-import org.eclipsefoundation.git.eca.model.Project.GitlabProject;
-import org.eclipsefoundation.git.eca.model.Project.User;
+import org.eclipsefoundation.git.eca.api.models.Project.GitlabProject;
+import org.eclipsefoundation.git.eca.api.models.Project.User;
 
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/Project.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java
similarity index 99%
rename from src/main/java/org/eclipsefoundation/git/eca/model/Project.java
rename to src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java
index ba5be582840bfc05fcd2fd1db3f8dddcb083b7dd..fa07ce5d1a85dfe6aa2814978606a02ca2dd1e13 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/model/Project.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java
@@ -9,7 +9,7 @@
 *
 * SPDX-License-Identifier: EPL-2.0
 **********************************************************************/
-package org.eclipsefoundation.git.eca.model;
+package org.eclipsefoundation.git.eca.api.models;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/SystemHook.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/SystemHook.java
similarity index 98%
rename from src/main/java/org/eclipsefoundation/git/eca/model/SystemHook.java
rename to src/main/java/org/eclipsefoundation/git/eca/api/models/SystemHook.java
index b352d59bc7dd24e991d62030c2f22d82bee6ff19..ddc7fd66b391baba7b8639024ea5ca8ea537d289 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/model/SystemHook.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/SystemHook.java
@@ -9,7 +9,7 @@
 *
 * SPDX-License-Identifier: EPL-2.0
 **********************************************************************/
-package org.eclipsefoundation.git.eca.model;
+package org.eclipsefoundation.git.eca.api.models;
 
 import java.time.ZonedDateTime;
 import java.util.List;
diff --git a/src/main/java/org/eclipsefoundation/git/eca/config/BCSecurityProvider.java b/src/main/java/org/eclipsefoundation/git/eca/config/BCSecurityProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..ee86744b73a93f6bb175f8dfdb5ddb8353ebfa05
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/config/BCSecurityProvider.java
@@ -0,0 +1,37 @@
+/**
+ * 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.config;
+
+import java.security.Security;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Singleton;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import io.quarkus.runtime.Startup;
+
+/**
+ * Adds Bouncycastle (BC) as a security provider to enable PKCS1 consumption.
+ * 
+ * @author Martin Lowe
+ *
+ */
+@Startup
+@Singleton
+public class BCSecurityProvider {
+
+    @PostConstruct
+    public void init() {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java b/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java
index 8831591feba1faa8a87f8765b652d802da9e856a..0650a2e89a34d6c0c53f4fea19defbbac3c79a8f 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java
@@ -16,8 +16,8 @@ import java.util.stream.Collectors;
 
 import javax.ws.rs.core.MultivaluedMap;
 
+import org.eclipsefoundation.git.eca.api.models.Project;
 import org.eclipsefoundation.git.eca.model.Commit;
-import org.eclipsefoundation.git.eca.model.Project;
 import org.eclipsefoundation.git.eca.model.ValidationRequest;
 import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames;
 import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
diff --git a/src/main/java/org/eclipsefoundation/git/eca/helper/JwtHelper.java b/src/main/java/org/eclipsefoundation/git/eca/helper/JwtHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a737d7394d9e85d324c247aaf6d935e4e17db05
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/helper/JwtHelper.java
@@ -0,0 +1,58 @@
+/**
+ * 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.helper;
+
+import java.io.FileReader;
+import java.nio.file.Paths;
+import java.security.PrivateKey;
+
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper to load external resources as a JWT key.
+ * 
+ * @author Martin Lowe
+ *
+ */
+public class JwtHelper {
+    private static final Logger LOGGER = LoggerFactory.getLogger(JwtHelper.class);
+
+    /**
+     * Reads in external PEM keys in a way that supports both PKCS#1 and PKCS#8. This is needed as the GH-provided RSA key
+     * is encoded using PKCS#1 and is not available for consumption in OOTB Java/smallrye, and smallrye's default PEM reader
+     * only reads within the resources path, so external file support isn't really available.
+     * 
+     * Src: https://stackoverflow.com/questions/41934846/read-rsa-private-key-of-format-pkcs1-in-java
+     * 
+     * @param location the location of the file
+     * @return the PrivateKey instance for the PEM file at the location, or null if it could not be read/parsed.
+     */
+    public static PrivateKey getExternalPrivateKey(String location) {
+        // create auto-closing reading resources for the external PEM file
+        try (FileReader keyReader = new FileReader(Paths.get(location).toFile()); PEMParser pemParser = new PEMParser(keyReader)) {
+            // use the BouncyCastle provider for PKCS#1 support (not available ootb)
+            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
+            // create the key and retrieve the PrivateKey portion
+            return converter.getKeyPair((PEMKeyPair) pemParser.readObject()).getPrivate();
+        } catch (Exception e) {
+            LOGGER.error("Error while loading private pem", e);
+        }
+        return null;
+    }
+
+    private JwtHelper() {
+    }
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/PrivateProjectData.java b/src/main/java/org/eclipsefoundation/git/eca/model/PrivateProjectData.java
index 2a4215a4b50b7c3e7831feb57775a38ea2defd17..b46f7b58984fea61a0b4a90b94cdab1b70f0151d 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/model/PrivateProjectData.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/model/PrivateProjectData.java
@@ -20,6 +20,12 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
 import com.google.auto.value.AutoValue;
 
+/**
+ * Model for Gitlab private project report logs. Used to report on historic projects in the EF hosted Gitlab instance.
+ * 
+ * @author Zachary Sabourin
+ *
+ */
 @AutoValue
 @JsonDeserialize(builder = AutoValue_PrivateProjectData.Builder.class)
 public abstract class PrivateProjectData {
diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/mappers/BaseEntityMapper.java b/src/main/java/org/eclipsefoundation/git/eca/model/mappers/BaseEntityMapper.java
index 145e760742969e001c395d3b575d5f8f6c1a0b4d..0961f0faf560b7b19d625fa1bca824e7d045dd61 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/model/mappers/BaseEntityMapper.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/model/mappers/BaseEntityMapper.java
@@ -15,7 +15,6 @@ import org.eclipsefoundation.persistence.dao.PersistenceDao;
 import org.eclipsefoundation.persistence.dto.BareNode;
 import org.mapstruct.Context;
 import org.mapstruct.InheritInverseConfiguration;
-import org.mapstruct.MapperConfig;
 import org.mapstruct.ObjectFactory;
 import org.mapstruct.TargetType;
 
diff --git a/src/main/java/org/eclipsefoundation/git/eca/namespace/GithubCommitStatuses.java b/src/main/java/org/eclipsefoundation/git/eca/namespace/GithubCommitStatuses.java
new file mode 100644
index 0000000000000000000000000000000000000000..2017fca8988bfccb10fee240da2d03e53f3800c4
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/namespace/GithubCommitStatuses.java
@@ -0,0 +1,52 @@
+/**
+ * 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.namespace;
+
+/**
+ * Statuses used for Github commit status objects. Contains basic messaging used in the description of the status to be
+ * sent back to Github.
+ * 
+ * @author Martin Lowe
+ *
+ */
+public enum GithubCommitStatuses {
+
+    SUCCESS("The author(s) of the pull request is covered by necessary legal agreements in order to proceed!"),
+    FAILURE("An unexpected error has occurred. Please contact webmaster@eclipse.org."),
+    ERROR("The author(s) of the pull request is not covered by necessary legal agreements in order to proceed."),
+    PENDING("Eclipse Foundation Contributor Agreement validation is in progress.");
+
+    private String message;
+
+    /**
+     * Instantiate the enum status with the display message.
+     * 
+     * @param message the display message associated with the status.
+     */
+    private GithubCommitStatuses(String message) {
+        this.message = message;
+    }
+
+    /**
+     * Returns the message associated with the status.
+     * 
+     * @return the string message for status.
+     */
+    public String getMessage() {
+        return this.message;
+    }
+
+    @Override
+    public String toString() {
+        return this.name().toLowerCase();
+    }
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..26685b2d59badef91c41f62935599172820a5d08
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java
@@ -0,0 +1,211 @@
+/**
+ * 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.resource;
+
+import java.net.URI;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.eclipsefoundation.core.model.RequestWrapper;
+import org.eclipsefoundation.core.service.APIMiddleware;
+import org.eclipsefoundation.git.eca.api.GithubAPI;
+import org.eclipsefoundation.git.eca.api.models.GithubAccessToken;
+import org.eclipsefoundation.git.eca.api.models.GithubCommit;
+import org.eclipsefoundation.git.eca.api.models.GithubCommitStatusRequest;
+import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest;
+import org.eclipsefoundation.git.eca.helper.JwtHelper;
+import org.eclipsefoundation.git.eca.model.Commit;
+import org.eclipsefoundation.git.eca.model.GitUser;
+import org.eclipsefoundation.git.eca.model.ValidationRequest;
+import org.eclipsefoundation.git.eca.model.ValidationResponse;
+import org.eclipsefoundation.git.eca.namespace.GithubCommitStatuses;
+import org.eclipsefoundation.git.eca.namespace.ProviderType;
+import org.eclipsefoundation.git.eca.service.ValidationService;
+import org.jboss.resteasy.annotations.jaxrs.HeaderParam;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.quarkus.cache.CacheResult;
+import io.smallrye.jwt.auth.principal.JWTParser;
+import io.smallrye.jwt.build.Jwt;
+
+/**
+ * Resource for processing Github pull request events, used to update commit status entries for the Git ECA application.
+ * 
+ * @author Martin Lowe
+ *
+ */
+@Path("webhooks/github")
+public class GithubWebhooksResource {
+    private static final Logger LOGGER = LoggerFactory.getLogger(GithubWebhooksResource.class);
+
+    @ConfigProperty(name = "smallrye.jwt.sign.key.location")
+    String location;
+    @ConfigProperty(name = "eclipse.webhooks.github.context")
+    String context;
+    @ConfigProperty(name = "eclipse.webhooks.github.server-target")
+    String serverTarget;
+
+    @ConfigProperty(name = "eclipse.github.default-api-version", defaultValue = "2022-11-28")
+    String apiVersion;
+
+    @Inject
+    RequestWrapper wrapper;
+    @Inject
+    APIMiddleware middleware;
+    @Inject
+    ValidationService validationService;
+
+    @Inject
+    JWTParser parser;
+
+    @RestClient
+    GithubAPI ghApi;
+
+    /**
+     * Entry point for processing Github webhook requests. Accepts standard fields as described in the <a href=
+     * "https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#webhook-payload-object-common-properties">Webhook
+     * properties documentation</a>, as well as the <a href=
+     * "https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request">Pull
+     * Request event type documentation.</a>
+     * 
+     * @param deliveryId Github provided unique delivery ID
+     * @param eventType the type of webhook event that was fired
+     * @param request the webhook request body.
+     * @return an OK status when done processing.
+     */
+    @POST
+    @Path("check")
+    public Response processGithubCheck(@HeaderParam("X-GitHub-Delivery") String deliveryId, @HeaderParam("X-GitHub-Event") String eventType,
+            GithubWebhookRequest request) {
+        // If event isn't a pr event, drop as we don't process them
+        if (!"pull_request".equalsIgnoreCase(eventType)) {
+            return Response.ok().build();
+        }
+        LOGGER.trace("Processing PR event for install {} with delivery ID of {}", request.getInstallation().getId(), deliveryId);
+        // start validation process
+        ValidationRequest vr = generateRequest(request);
+        String fingerprint = validationService.generateRequestHash(vr);
+        updateCommitStatus(request, GithubCommitStatuses.PENDING, fingerprint);
+
+        // validate the response
+        ValidationResponse r = validationService.validateIncomingRequest(vr, wrapper);
+        if (r.getPassed()) {
+            updateCommitStatus(request, GithubCommitStatuses.SUCCESS, fingerprint);
+        } else {
+            updateCommitStatus(request, GithubCommitStatuses.FAILURE, fingerprint);
+        }
+        return Response.ok().build();
+    }
+
+    /**
+     * Sends off a POST to update the commit status given a context for the current PR.
+     * 
+     * @param request the current webhook update request
+     * @param state the state to set the status to
+     * @param fingerprint the internal unique string for the set of commits being processed
+     */
+    private void updateCommitStatus(GithubWebhookRequest request, GithubCommitStatuses state, String fingerprint) {
+        LOGGER
+                .trace("Generated access token for installation {}: {}", request.getInstallation().getId(),
+                        getAccessToken(request.getInstallation().getId()).getToken());
+        ghApi
+                .updateStatus(getBearerString(request.getInstallation().getId()), apiVersion, request.getRepository().getFullName(),
+                        request.getPullRequest().getHead().getSha(),
+                        GithubCommitStatusRequest
+                                .builder()
+                                .setDescription(state.getMessage())
+                                .setState(state.toString())
+                                .setTargetUrl(serverTarget + "/git/eca/status/" + fingerprint + "/ui")
+                                .setContext(context)
+                                .build());
+    }
+
+    /**
+     * Generate the validation request for the current GH Webhook request.
+     * 
+     * @param request the current webhook request for validation
+     * @return the Validation Request body to be used in validating data
+     */
+    private ValidationRequest generateRequest(GithubWebhookRequest request) {
+        // get the commits that will be validated, don't cache as changes can come in too fast for it to be useful
+        List<GithubCommit> commits = middleware
+                .getAll(i -> ghApi
+                        .getCommits(getBearerString(request.getInstallation().getId()), apiVersion, request.getRepository().getFullName(),
+                                request.getPullRequest().getNumber()),
+                        GithubCommit.class);
+        LOGGER
+                .trace("Retrieved {} commits for PR {} in repo {}", commits.size(), request.getPullRequest().getNumber(),
+                        request.getRepository().getHtmlUrl());
+        // set up the validation request from current data
+        return ValidationRequest
+                .builder()
+                .setProvider(ProviderType.GITHUB)
+                .setRepoUrl(URI.create(request.getRepository().getHtmlUrl()))
+                .setCommits(commits
+                        .stream()
+                        .map(c -> Commit
+                                .builder()
+                                .setHash(c.getSha())
+                                .setAuthor(GitUser
+                                        .builder()
+                                        .setMail(c.getCommit().getAuthor().getEmail())
+                                        .setName(c.getCommit().getAuthor().getName())
+                                        .build())
+                                .setCommitter(GitUser
+                                        .builder()
+                                        .setMail(c.getCommit().getCommitter().getEmail())
+                                        .setName(c.getCommit().getCommitter().getName())
+                                        .build())
+                                .build())
+                        .collect(Collectors.toList()))
+                .build();
+    }
+
+    /**
+     * Retrieve the bearer token string for the current installation.
+     * 
+     * @param installationId the installation to generate a bearer string for
+     * @return the bearer string for the current request
+     */
+    protected String getBearerString(String installationId) {
+        return "Bearer " + getAccessToken(installationId).getToken();
+    }
+
+    /**
+     * Retrieve a new cached Github access token, using a JWT generated from the GH provided PKCS#1 private key.
+     * 
+     * @param installationId the installation that the access token is being generated for
+     * @return the access token to be used in the requests.
+     */
+    @CacheResult(cacheName = "accesstoken")
+    protected GithubAccessToken getAccessToken(String installationId) {
+        return ghApi.getNewAccessToken("Bearer " + generateJwt(), apiVersion, installationId);
+    }
+
+    /**
+     * Generate the JWT to use for Github requests. This is stored in a special one-shot cache method secured to this class,
+     * and not to be used for other requests that require JWTs.
+     * 
+     * @return signed JWT using the issuer and secret defined in the secret properties.
+     */
+    private String generateJwt() {
+        return Jwt.subject("EclipseWebmaster").sign(JwtHelper.getExternalPrivateKey(location));
+    }
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java
index 6345a0f67b373a4d25a2ebc35a95e6fa7a5d0290..7985ad0a78d5c826edf8ec3dc8daf40571dc90b6 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java
@@ -31,8 +31,8 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.eclipsefoundation.core.model.RequestWrapper;
 import org.eclipsefoundation.core.service.CachingService;
 import org.eclipsefoundation.git.eca.api.models.EclipseUser;
+import org.eclipsefoundation.git.eca.api.models.Project;
 import org.eclipsefoundation.git.eca.dto.CommitValidationStatus;
-import org.eclipsefoundation.git.eca.model.Project;
 import org.eclipsefoundation.git.eca.model.ValidationRequest;
 import org.eclipsefoundation.git.eca.model.ValidationResponse;
 import org.eclipsefoundation.git.eca.namespace.APIStatusCode;
@@ -48,9 +48,8 @@ import io.quarkus.qute.Location;
 import io.quarkus.qute.Template;
 
 /**
- * ECA validation endpoint for Git commits. Will use information from the bots,
- * projects, and accounts API to validate commits passed to this endpoint.
- * Should be as system agnostic as possible to allow for any service to request
+ * ECA validation endpoint for Git commits. Will use information from the bots, projects, and accounts API to validate
+ * commits passed to this endpoint. Should be as system agnostic as possible to allow for any service to request
  * validation with less reliance on services external to the Eclipse foundation.
  *
  * @author Martin Lowe, Zachary Sabourin
@@ -88,16 +87,14 @@ public class ValidationResource {
     Template membershipTemplateWeb;
 
     /**
-     * Consuming a JSON request, this method will validate all passed commits, using
-     * the repo URL and the repository provider. These commits will be validated to
-     * ensure that all users are covered either by an ECA, or are committers on the
-     * project. In the case of ECA-only contributors, an additional sign off footer
-     * is required in the body of the commit.
+     * Consuming a JSON request, this method will validate all passed commits, using the repo URL and the repository
+     * provider. These commits will be validated to ensure that all users are covered either by an ECA, or are committers on
+     * the project. In the case of ECA-only contributors, an additional sign off footer is required in the body of the
+     * commit.
      *
      * @param req the request containing basic data plus the commits to be validated
-     * @return a web response indicating success or failure for each commit, along
-     *         with standard messages that may be
-     *         used to give users context on failure.
+     * @return a web response indicating success or failure for each commit, along with standard messages that may be used
+     * to give users context on failure.
      * @throws MalformedURLException
      */
     @POST
@@ -110,7 +107,7 @@ public class ValidationResource {
         } else {
             // create a stubbed response with the errors
             ValidationResponse out = ValidationResponse.builder().build();
-            messages.forEach(m -> out.addError(m, null,APIStatusCode.ERROR_DEFAULT));
+            messages.forEach(m -> out.addError(m, null, APIStatusCode.ERROR_DEFAULT));
             return out.toResponse();
         }
     }
@@ -129,18 +126,19 @@ public class ValidationResource {
         if (statuses.isEmpty()) {
             return Response.status(404).build();
         }
-        List<Project> ps = projects.retrieveProjectsForRepoURL(statuses.get(0).getRepoUrl(),
-                statuses.get(0).getProvider());
-        return Response.ok().entity(membershipTemplateWeb.data("statuses", statuses, "repoUrl",
-                statuses.get(0).getRepoUrl(), "project", ps.isEmpty() ? null : ps.get(0)).render()).build();
+        List<Project> ps = projects.retrieveProjectsForRepoURL(statuses.get(0).getRepoUrl(), statuses.get(0).getProvider());
+        return Response
+                .ok()
+                .entity(membershipTemplateWeb
+                        .data("statuses", statuses, "repoUrl", statuses.get(0).getRepoUrl(), "project", ps.isEmpty() ? null : ps.get(0))
+                        .render())
+                .build();
     }
 
     @GET
     @Path("/lookup")
     public Response getUserStatus(@QueryParam("email") String email) {
-
         EclipseUser user = users.getUser(email);
-
         if (Objects.isNull(user)) {
             return Response.status(404).build();
         }
@@ -148,17 +146,15 @@ public class ValidationResource {
         if (!user.getECA().getSigned()) {
             return Response.status(403).build();
         }
-
         return Response.ok().build();
     }
 
     /**
-     * Check if there are any issues with the validation request, returning error
-     * messages if there are issues with the request.
+     * Check if there are any issues with the validation request, returning error messages if there are issues with the
+     * request.
      * 
      * @param req the current validation request
-     * @return a list of error messages to report, or an empty list if there are no
-     *         errors with the request.
+     * @return a list of error messages to report, or an empty list if there are no errors with the request.
      */
     private List<String> checkRequest(ValidationRequest req) {
         // check that we have commits to validate
diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/WebhooksResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/WebhooksResource.java
index c94fc8bbcead93af5ab688faad8cd08337c3f5c0..a9fd5b266d9eae8d1544371f00cef23fa701fcc7 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/resource/WebhooksResource.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/resource/WebhooksResource.java
@@ -17,7 +17,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.core.Response;
 
 import org.eclipsefoundation.core.model.RequestWrapper;
-import org.eclipsefoundation.git.eca.model.SystemHook;
+import org.eclipsefoundation.git.eca.api.models.SystemHook;
 import org.eclipsefoundation.git.eca.namespace.EventType;
 import org.eclipsefoundation.git.eca.service.SystemHookService;
 import org.jboss.resteasy.annotations.jaxrs.HeaderParam;
@@ -27,14 +27,12 @@ import org.slf4j.LoggerFactory;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
-@Path("/webhooks")
+@Path("webhooks/gitlab")
 public class WebhooksResource {
-
     private static final Logger LOGGER = LoggerFactory.getLogger(WebhooksResource.class);
 
     @Inject
     RequestWrapper wrapper;
-
     @Inject
     SystemHookService hookService;
 
@@ -42,30 +40,26 @@ public class WebhooksResource {
     ObjectMapper om;
 
     @POST
-    @Path("/gitlab/system")
+    @Path("system")
     public Response processGitlabHook(@HeaderParam("X-Gitlab-Event") String eventHeader, String jsonBody) {
-
         // Do not process if header is incorrect
-        if (!hasValidHeader(eventHeader)) {
+        if (!"system hook".equalsIgnoreCase(eventHeader)) {
             return Response.ok().build();
         }
 
+        // based on event name in body, process the event hook
         String eventName = readEventName(jsonBody);
         EventType type = EventType.getType(eventName);
-
         switch (type) {
             case PROJECT_CREATE:
                 hookService.processProjectCreateHook(wrapper, convertRequestToHook(jsonBody));
                 break;
-
             case PROJECT_DESTROY:
                 hookService.processProjectDeleteHook(wrapper, convertRequestToHook(jsonBody));
                 break;
-
             case PROJECT_RENAME:
                 hookService.processProjectRenameHook(wrapper, convertRequestToHook(jsonBody));
                 break;
-
             case UNSUPPORTED:
             default:
                 LOGGER.trace("Dropped event: {}", eventName);
@@ -76,8 +70,8 @@ public class WebhooksResource {
     }
 
     /**
-     * Processes the json body contents and converts them to a SystemHook object.
-     * Returns null if the json body couldn't be processed.
+     * Processes the json body contents and converts them to a SystemHook object. Returns null if the json body couldn't be
+     * processed.
      * 
      * @param jsonBody the json body as a string
      * @return A SystemHook object converted from a json string
@@ -92,9 +86,8 @@ public class WebhooksResource {
     }
 
     /**
-     * Processes the json body contents and returns the event_name field. Returns
-     * null if the was a processing error or if the event_name field is
-     * null/missing.
+     * Processes the json body contents and returns the event_name field. Returns null if the was a processing error or if
+     * the event_name field is null/missing.
      * 
      * @param jsonBody the json body as a String
      * @return the event_name field
@@ -107,14 +100,4 @@ public class WebhooksResource {
             return null;
         }
     }
-
-    /**
-     * Validates that the hook header contains the required value
-     * 
-     * @param eventHeader the event header value
-     * @return true if valid, false if not
-     */
-    private boolean hasValidHeader(String eventHeader) {
-        return eventHeader != null && eventHeader.equalsIgnoreCase("system hook");
-    }
 }
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java b/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java
index 9efdc637487785aa1e7d9f9a61d97ce25f978076..8c2d182b10bf40196da01e8d4ce116339e34d6da 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java
@@ -2,8 +2,8 @@ package org.eclipsefoundation.git.eca.service;
 
 import java.util.List;
 
-import org.eclipsefoundation.git.eca.model.InterestGroupData;
-import org.eclipsefoundation.git.eca.model.Project;
+import org.eclipsefoundation.git.eca.api.models.InterestGroupData;
+import org.eclipsefoundation.git.eca.api.models.Project;
 
 /**
  * Service for retrieving and interacting with interest groups.
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java b/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java
index 925ddfda35a9dee3a55b5052cda6cd19b6d7ed92..54c2199f477ffbe7b149a485617aaf04ad108b74 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java
@@ -13,7 +13,7 @@ package org.eclipsefoundation.git.eca.service;
 
 import java.util.List;
 
-import org.eclipsefoundation.git.eca.model.Project;
+import org.eclipsefoundation.git.eca.api.models.Project;
 import org.eclipsefoundation.git.eca.model.ValidationRequest;
 import org.eclipsefoundation.git.eca.namespace.ProviderType;
 
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/SystemHookService.java b/src/main/java/org/eclipsefoundation/git/eca/service/SystemHookService.java
index 2ec8bdc54510a482b4c0794b79cc820f6c730b58..3ddab46e18020a1add565b1d7a2f3be9e2cf63ec 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/SystemHookService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/SystemHookService.java
@@ -12,7 +12,7 @@
 package org.eclipsefoundation.git.eca.service;
 
 import org.eclipsefoundation.core.model.RequestWrapper;
-import org.eclipsefoundation.git.eca.model.SystemHook;
+import org.eclipsefoundation.git.eca.api.models.SystemHook;
 
 /**
  * Processes the various system hooks received.
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java
index 1c352bfc0e2bb8ad0957e506c481b68a910d413e..06c01484868c90cb1d02529ff13e03990bf95226 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java
@@ -14,7 +14,7 @@ package org.eclipsefoundation.git.eca.service;
 import java.util.List;
 
 import org.eclipsefoundation.git.eca.api.models.EclipseUser;
-import org.eclipsefoundation.git.eca.model.Project;
+import org.eclipsefoundation.git.eca.api.models.Project;
 
 public interface UserService {
 
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java b/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java
index 55b9cb902d84cff2b63d92232e766c6e48a5bf96..0110228cebf0d2544c337cd6a2614f798fdf6836 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java
@@ -16,8 +16,8 @@ import java.security.NoSuchAlgorithmException;
 import java.util.List;
 
 import org.eclipsefoundation.core.model.RequestWrapper;
+import org.eclipsefoundation.git.eca.api.models.Project;
 import org.eclipsefoundation.git.eca.dto.CommitValidationStatus;
-import org.eclipsefoundation.git.eca.model.Project;
 import org.eclipsefoundation.git.eca.model.ValidationRequest;
 import org.eclipsefoundation.git.eca.model.ValidationResponse;
 
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java
index dc4427f09ac477f376f7dac5a5e826703cbea5c3..54e841859933d0621809aa88d4f2b3d9e5790266 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java
@@ -31,7 +31,7 @@ import org.eclipsefoundation.core.service.CachingService;
 import org.eclipsefoundation.git.eca.api.AccountsAPI;
 import org.eclipsefoundation.git.eca.api.BotsAPI;
 import org.eclipsefoundation.git.eca.api.models.EclipseUser;
-import org.eclipsefoundation.git.eca.model.Project;
+import org.eclipsefoundation.git.eca.api.models.Project;
 import org.eclipsefoundation.git.eca.service.OAuthService;
 import org.eclipsefoundation.git.eca.service.UserService;
 import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java
index 0fc98e656e79e59a47a9ceaacdf3af8c601fcd5e..b4c113c8ae22cc2d4791eb8ecb277958e2607cdd 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java
@@ -11,8 +11,8 @@ import org.eclipse.microprofile.rest.client.inject.RestClient;
 import org.eclipsefoundation.core.service.APIMiddleware;
 import org.eclipsefoundation.core.service.CachingService;
 import org.eclipsefoundation.git.eca.api.ProjectsAPI;
-import org.eclipsefoundation.git.eca.model.InterestGroupData;
-import org.eclipsefoundation.git.eca.model.Project;
+import org.eclipsefoundation.git.eca.api.models.InterestGroupData;
+import org.eclipsefoundation.git.eca.api.models.Project;
 import org.eclipsefoundation.git.eca.service.InterestGroupService;
 import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultSystemHookService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultSystemHookService.java
index f56aabf8a1bf5f07a3827e617d21695346b17386..394f717a485311cd7750eb6b395d039ebbdff75b 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultSystemHookService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultSystemHookService.java
@@ -34,8 +34,8 @@ import org.eclipsefoundation.core.service.CachingService;
 import org.eclipsefoundation.git.eca.api.GitlabAPI;
 import org.eclipsefoundation.git.eca.api.models.GitlabProjectResponse;
 import org.eclipsefoundation.git.eca.api.models.GitlabUserResponse;
+import org.eclipsefoundation.git.eca.api.models.SystemHook;
 import org.eclipsefoundation.git.eca.dto.PrivateProjectEvent;
-import org.eclipsefoundation.git.eca.model.SystemHook;
 import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames;
 import org.eclipsefoundation.git.eca.service.SystemHookService;
 import org.eclipsefoundation.persistence.dao.PersistenceDao;
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java
index 1d09d596e04432b25d2055d4a6ba9d3a22411a6d..effafd290e8399bc0573d7e935504a5a7f5b8657 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java
@@ -29,13 +29,13 @@ import org.eclipsefoundation.core.helper.DateTimeHelper;
 import org.eclipsefoundation.core.model.RequestWrapper;
 import org.eclipsefoundation.core.service.CachingService;
 import org.eclipsefoundation.git.eca.api.models.EclipseUser;
+import org.eclipsefoundation.git.eca.api.models.Project;
 import org.eclipsefoundation.git.eca.dto.CommitValidationMessage;
 import org.eclipsefoundation.git.eca.dto.CommitValidationStatus;
 import org.eclipsefoundation.git.eca.dto.CommitValidationStatusGrouping;
 import org.eclipsefoundation.git.eca.helper.CommitHelper;
 import org.eclipsefoundation.git.eca.model.Commit;
 import org.eclipsefoundation.git.eca.model.GitUser;
-import org.eclipsefoundation.git.eca.model.Project;
 import org.eclipsefoundation.git.eca.model.ValidationRequest;
 import org.eclipsefoundation.git.eca.model.ValidationResponse;
 import org.eclipsefoundation.git.eca.namespace.APIStatusCode;
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java
index a73f02afe46bd6127893e98474eca78543ff1084..d360d2e0d739a1f597175e99efcaf62e9682e937 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java
@@ -29,7 +29,7 @@ import org.eclipse.microprofile.rest.client.inject.RestClient;
 import org.eclipsefoundation.core.service.APIMiddleware;
 import org.eclipsefoundation.core.service.CachingService;
 import org.eclipsefoundation.git.eca.api.ProjectsAPI;
-import org.eclipsefoundation.git.eca.model.Project;
+import org.eclipsefoundation.git.eca.api.models.Project;
 import org.eclipsefoundation.git.eca.model.ValidationRequest;
 import org.eclipsefoundation.git.eca.namespace.ProviderType;
 import org.eclipsefoundation.git.eca.service.InterestGroupService;
@@ -58,6 +58,8 @@ import io.quarkus.runtime.Startup;
 public class PaginationProjectsService implements ProjectsService {
     private static final Logger LOGGER = LoggerFactory.getLogger(PaginationProjectsService.class);
 
+    @ConfigProperty(name = "eclipse.projects.precache.enabled", defaultValue = "true")
+    boolean isEnabled;
     @ConfigProperty(name = "cache.pagination.refresh-frequency-seconds", defaultValue = "3600")
     long refreshAfterWrite;
 
@@ -117,12 +119,14 @@ public class PaginationProjectsService implements ProjectsService {
                     }
                 });
 
-        // pre-cache the projects to reduce load time for other users
-        LOGGER.debug("Starting pre-cache of projects");
-        if (getProjects() == null) {
-            LOGGER.warn("Unable to populate pre-cache for Eclipse projects. Calls may experience degraded performance.");
+        if (isEnabled) {
+            // pre-cache the projects to reduce load time for other users
+            LOGGER.debug("Starting pre-cache of projects");
+            if (getProjects() == null) {
+                LOGGER.warn("Unable to populate pre-cache for Eclipse projects. Calls may experience degraded performance.");
+            }
+            LOGGER.debug("Completed pre-cache of projects assets");
         }
-        LOGGER.debug("Completed pre-cache of projects assets");
     }
 
     @Override
diff --git a/src/main/resources/META-INF/resources/favicon.ico b/src/main/resources/META-INF/resources/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..bb43f009ecaac5bac6feda26839f664145915392
Binary files /dev/null and b/src/main/resources/META-INF/resources/favicon.ico differ
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 3ffafdbffcc2a1def1397653aa514c824d1047f2..6d7f3ddad721bbbd8496c12365ea8b108a7180a2 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -36,6 +36,15 @@ quarkus.cache.caffeine."default".expire-after-write=1H
 quarkus.cache.caffeine."record".initial-capacity=${quarkus.cache.caffeine."default".initial-capacity}
 quarkus.cache.caffeine."record".expire-after-write=${quarkus.cache.caffeine."default".expire-after-write}
 
+## JWT cache, 115 second cache time to make sure there is no accidental sending of an expired token
+quarkus.cache.caffeine."accesstoken".initial-capacity=1000
+quarkus.cache.caffeine."accesstoken".expire-after-write=119S
+## JWT Placeholders/defaults
+smallrye.jwt.new-token.lifespan=120
+smallrye.jwt.new-token.issuer=262450
+eclipse.webhooks.github.context=eclipsefdn/eca
+eclipse.webhooks.github.server-target=https://api.eclipse.org
+
 eclipse.mail.allowlist=noreply@github.com,49699333+dependabot[bot]@users.noreply.github.com
 %dev.eclipse.cache.resource.enabled=true
 %dev.eclipse.optional-resources.enabled=true
diff --git a/src/test/java/org/eclipsefoundation/git/eca/resource/WebhoooksResourceTest.java b/src/test/java/org/eclipsefoundation/git/eca/resource/WebhoooksResourceTest.java
index dd0f657beea8c404ed21c7e6d11f0085f6aa7fcf..23a1d312f08ed6993510273de208699eef1c70fa 100644
--- a/src/test/java/org/eclipsefoundation/git/eca/resource/WebhoooksResourceTest.java
+++ b/src/test/java/org/eclipsefoundation/git/eca/resource/WebhoooksResourceTest.java
@@ -16,8 +16,8 @@ import java.util.Arrays;
 import java.util.Map;
 import java.util.Optional;
 
-import org.eclipsefoundation.git.eca.model.SystemHook;
-import org.eclipsefoundation.git.eca.model.SystemHook.Owner;
+import org.eclipsefoundation.git.eca.api.models.SystemHook;
+import org.eclipsefoundation.git.eca.api.models.SystemHook.Owner;
 import org.eclipsefoundation.testing.helpers.TestCaseHelper;
 import org.eclipsefoundation.testing.templates.RestAssuredTemplates;
 import org.eclipsefoundation.testing.templates.RestAssuredTemplates.EndpointTestCase;
diff --git a/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java b/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java
index a59776fe73c882febccc5dc2044f26b00ea732ea..434fcec2bd044dff0a3de619f2649df1b2628451 100644
--- a/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java
+++ b/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java
@@ -19,7 +19,7 @@ import java.util.Optional;
 import javax.inject.Inject;
 
 import org.eclipsefoundation.git.eca.api.models.EclipseUser;
-import org.eclipsefoundation.git.eca.model.Project;
+import org.eclipsefoundation.git.eca.api.models.Project;
 import org.eclipsefoundation.git.eca.service.ProjectsService;
 import org.eclipsefoundation.git.eca.service.UserService;
 import org.junit.jupiter.api.Assertions;
diff --git a/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java b/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java
index 0ccade49969a1c8c20f418d184df542225feca54..3ebbabcebc99b3cc6f9fa62801ff5139d66fafd8 100644
--- a/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java
+++ b/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java
@@ -16,8 +16,8 @@ import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
-import org.eclipsefoundation.git.eca.model.Project;
-import org.eclipsefoundation.git.eca.model.Project.Repo;
+import org.eclipsefoundation.git.eca.api.models.Project;
+import org.eclipsefoundation.git.eca.api.models.Project.Repo;
 import org.eclipsefoundation.git.eca.service.ProjectsService;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java
index 103487d5d1f899fc64fc418fe97f31636f30d898..477248204ec451030cdd24f947aad35794053bbe 100644
--- a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java
+++ b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java
@@ -25,12 +25,12 @@ import javax.ws.rs.core.Response;
 import org.eclipse.microprofile.rest.client.inject.RestClient;
 import org.eclipsefoundation.core.service.APIMiddleware.BaseAPIParameters;
 import org.eclipsefoundation.git.eca.api.ProjectsAPI;
-import org.eclipsefoundation.git.eca.model.InterestGroupData;
-import org.eclipsefoundation.git.eca.model.InterestGroupData.Descriptor;
-import org.eclipsefoundation.git.eca.model.Project;
-import org.eclipsefoundation.git.eca.model.Project.GitlabProject;
-import org.eclipsefoundation.git.eca.model.Project.Repo;
-import org.eclipsefoundation.git.eca.model.Project.User;
+import org.eclipsefoundation.git.eca.api.models.InterestGroupData;
+import org.eclipsefoundation.git.eca.api.models.Project;
+import org.eclipsefoundation.git.eca.api.models.InterestGroupData.Descriptor;
+import org.eclipsefoundation.git.eca.api.models.Project.GitlabProject;
+import org.eclipsefoundation.git.eca.api.models.Project.Repo;
+import org.eclipsefoundation.git.eca.api.models.Project.User;
 
 import io.quarkus.test.Mock;
 
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index 78bcff0f9e879446d9ab2f2e3e2963e40106041b..b127c9e09da4a30f2e6d6eb2712594270b831a69 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -25,3 +25,4 @@ quarkus.http.port=8080
 quarkus.oidc.enabled=false
 quarkus.keycloak.devservices.enabled=false
 quarkus.oidc-client.enabled=false
+smallrye.jwt.sign.key.location=test.pem
\ No newline at end of file