diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 68820a59c9524000cdc9aaca1c894d11f5836888..30825df0ed200a3dadd94dc2ecca2dfbaaee0a0f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -19,6 +19,8 @@ variables:
     IBM_HELM_CONFIG_PATH: devops/ibm/ibm-notification-config
     IBM_HELM_DEPLOY_PATH: devops/ibm/ibm-notification-deploy
 
+    ACCEPTANCE_TEST_DIR: "notification-acceptance-test"
+
 include:
     - project: "osdu/platform/ci-cd-pipelines"
       file: "standard-setup.yml"
diff --git a/NOTICE b/NOTICE
index 308cfc73e2c28da781e67f8ee4d99a747ccb04c3..6ae25fd454ad7dffe4a8a0cb5f907eb2bc70bf73 100644
--- a/NOTICE
+++ b/NOTICE
@@ -102,7 +102,7 @@ The following software have components provided under the terms of this license:
 - Jackson-dataformat-YAML (from https://github.com/FasterXML/jackson, https://github.com/FasterXML/jackson-dataformats-text)
 - Jackson-module-parameter-names (from https://repo1.maven.org/maven2/com/fasterxml/jackson/module/jackson-module-parameter-names)
 - Jakarta Dependency Injection (from https://github.com/eclipse-ee4j/injection-api)
-- Jakarta RESTful WS API (from https://github.com/eclipse-ee4j/jaxrs-api, https://maven.atlassian.com/3rdparty/jakarta/ws/rs/jakarta.ws.rs-api, https://repo1.maven.org/maven2/jakarta/ws/rs/jakarta.ws.rs-api)
+- Jakarta RESTful WS API (from https://github.com/eclipse-ee4j/jaxrs-api, https://repo1.maven.org/maven2/jakarta/ws/rs/jakarta.ws.rs-api)
 - Jakarta Servlet (from https://projects.eclipse.org/projects/ee4j.servlet)
 - Jakarta Validation API (from https://beanvalidation.org)
 - Java Architecture for XML Binding (from http://jaxb.java.net/, https://repo1.maven.org/maven2/javax/xml/bind/jaxb-api)
@@ -294,7 +294,7 @@ The following software have components provided under the terms of this license:
 - Jakarta Activation API (from https://github.com/eclipse-ee4j/jaf, https://github.com/jakartaee/jaf-api, https://repo1.maven.org/maven2/jakarta/activation/jakarta.activation-api)
 - Jakarta Annotations API (from https://projects.eclipse.org/projects/ee4j.ca)
 - Jakarta Messaging API (from https://projects.eclipse.org/projects/ee4j.jms)
-- Jakarta RESTful WS API (from https://github.com/eclipse-ee4j/jaxrs-api, https://maven.atlassian.com/3rdparty/jakarta/ws/rs/jakarta.ws.rs-api, https://repo1.maven.org/maven2/jakarta/ws/rs/jakarta.ws.rs-api)
+- Jakarta RESTful WS API (from https://github.com/eclipse-ee4j/jaxrs-api, https://repo1.maven.org/maven2/jakarta/ws/rs/jakarta.ws.rs-api)
 - Jakarta WebSocket - Client API (from https://projects.eclipse.org/projects/ee4j.websocket)
 - Jakarta WebSocket - Server API (from https://projects.eclipse.org/projects/ee4j.websocket, https://repo1.maven.org/maven2/org/jboss/spec/javax/websocket/jboss-websocket-api_1.1_spec)
 - Jakarta XML Binding API (from https://repo1.maven.org/maven2/jakarta/xml/bind/jakarta.xml.bind-api, https://repo1.maven.org/maven2/org/jboss/spec/javax/xml/bind/jboss-jaxb-api_2.3_spec)
@@ -401,7 +401,7 @@ The following software have components provided under the terms of this license:
 - Jakarta Annotations API (from https://projects.eclipse.org/projects/ee4j.ca)
 - Jakarta Dependency Injection (from https://github.com/eclipse-ee4j/injection-api)
 - Jakarta Messaging API (from https://projects.eclipse.org/projects/ee4j.jms)
-- Jakarta RESTful WS API (from https://github.com/eclipse-ee4j/jaxrs-api, https://maven.atlassian.com/3rdparty/jakarta/ws/rs/jakarta.ws.rs-api, https://repo1.maven.org/maven2/jakarta/ws/rs/jakarta.ws.rs-api)
+- Jakarta RESTful WS API (from https://github.com/eclipse-ee4j/jaxrs-api, https://repo1.maven.org/maven2/jakarta/ws/rs/jakarta.ws.rs-api)
 - Jakarta Servlet (from https://projects.eclipse.org/projects/ee4j.servlet)
 - Jakarta Validation API (from https://beanvalidation.org)
 - Jakarta WebSocket - Client API (from https://projects.eclipse.org/projects/ee4j.websocket)
@@ -419,7 +419,7 @@ The following software have components provided under the terms of this license:
 - Jakarta Annotations API (from https://projects.eclipse.org/projects/ee4j.ca)
 - Jakarta Dependency Injection (from https://github.com/eclipse-ee4j/injection-api)
 - Jakarta Messaging API (from https://projects.eclipse.org/projects/ee4j.jms)
-- Jakarta RESTful WS API (from https://github.com/eclipse-ee4j/jaxrs-api, https://maven.atlassian.com/3rdparty/jakarta/ws/rs/jakarta.ws.rs-api, https://repo1.maven.org/maven2/jakarta/ws/rs/jakarta.ws.rs-api)
+- Jakarta RESTful WS API (from https://github.com/eclipse-ee4j/jaxrs-api, https://repo1.maven.org/maven2/jakarta/ws/rs/jakarta.ws.rs-api)
 - Jakarta Servlet (from https://projects.eclipse.org/projects/ee4j.servlet)
 - Jakarta Validation API (from https://beanvalidation.org)
 - Jakarta WebSocket - Client API (from https://projects.eclipse.org/projects/ee4j.websocket)
@@ -551,7 +551,7 @@ efsl-1.0
 ========================================================================
 The following software have components provided under the terms of this license:
 
-- Jakarta RESTful WS API (from https://github.com/eclipse-ee4j/jaxrs-api, https://maven.atlassian.com/3rdparty/jakarta/ws/rs/jakarta.ws.rs-api, https://repo1.maven.org/maven2/jakarta/ws/rs/jakarta.ws.rs-api)
+- Jakarta RESTful WS API (from https://github.com/eclipse-ee4j/jaxrs-api, https://repo1.maven.org/maven2/jakarta/ws/rs/jakarta.ws.rs-api)
 
 ========================================================================
 gpl-2.0-classpath
diff --git a/devops/core-plus/pipeline/override-stages.yml b/devops/core-plus/pipeline/override-stages.yml
index 1a0a49ee0ac87ce15d4815e1ae552ee8f0726c61..689963d128961fd94630b243a9095f0804994c88 100644
--- a/devops/core-plus/pipeline/override-stages.yml
+++ b/devops/core-plus/pipeline/override-stages.yml
@@ -1,5 +1,6 @@
 variables:
   CORE_SERVICE: notification
+  GSA_TEST_ENABLED: "true"
 
 core-test:
   variables:
diff --git a/notification-acceptance-test/docs/README.md b/notification-acceptance-test/docs/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b685bab86318b894fac3a938131ad7b50260a0c3
--- /dev/null
+++ b/notification-acceptance-test/docs/README.md
@@ -0,0 +1,75 @@
+### Running E2E Tests
+
+You will need to have the following environment variables defined.
+
+| name                            | value                                                                                                            | description                                                                                                                                                  | sensitive? | source |
+|---------------------------------|------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|------------|--------|
+| `CLIENT_TENANT`                 | eg. `osdu`                                                                                                       | Client tenant used for testing                                                                                                                               | no         | -      |
+| `OSDU_TENANT`                   | eg. `osdu`                                                                                                       | OSDU tenant used for testing                                                                                                                                 | no         | -      |
+| `GROUP_ID`                      | eg. `group`                                                                                                      | Group ID used for testing. Group id, used in storage record ACL. Full group will be "data.default.viewers@{{data-partition-id}}.{{group_id}}"                | no         | -      |
+| `TOPIC_ID`                      | eg. `records-changed`                                                                                            | Topic ID                                                                                                                                                     | no         | -      |
+| `HMAC_SECRET`                   | eg. `123456789`                                                                                                  | String in hex , must match pattern ^[a-zA-Z0-9]{8,30}+$ & be in register variable SUBSCRIBER_SECRET                                                          | yes        | -      |
+| `NOTIFICATION_BASE_URL`         | eg. `https://osdu.core-dev.gcp.gnrg-osdu.projects.epam.com/api/notification/v1/`                                 | Endpoint of notification service                                                                                                                             | no         | -      |
+| `REGISTER_BASE_URL`             | eg. `https://osdu.core-dev.gcp.gnrg-osdu.projects.epam.com/api/register/v1`                                      | Endpoint of register service                                                                                                                                 | no         | -      |
+| `REGISTER_CUSTOM_PUSH_URL_HMAC` | eg. `https://osdu.core-dev.gcp.gnrg-osdu.projects.epam.com/api/register/v1/test/challenge/hmac-integration-test` | Endpoint of register custom push URL HMAC                                                                                                                    | no         | -      |
+| `LEGAL_HOST`                    | eg. `https://osdu.core-dev.gcp.gnrg-osdu.projects.epam.com/api/legal/v1`                                         | Endpoint of legal service                                                                                                                                    | no         | -      |
+| `STORAGE_HOST`                  | eg. `https://osdu.core-dev.gcp.gnrg-osdu.projects.epam.com/api/storage/v2`                                       | Endpoint of storage service                                                                                                                                  | no         | -      |
+| `GSA_TEST_ENABLED`              | eg. `true` or `false`                                                                                            | Enabler for GSA tests                                                                                                                                        | no         | -      |
+Authentication can be provided as OIDC config:
+
+| name                                           | value                                   | description                                           | sensitive? | source |
+|------------------------------------------------|-----------------------------------------|-------------------------------------------------------|------------|--------|
+| `TEST_DE_ADMIN_OPENID_PROVIDER_CLIENT_ID`      | `********`                              | Service account client id which has admins api access | yes        | -      |
+| `TEST_DE_ADMIN_OPENID_PROVIDER_CLIENT_SECRET`  | `********`                              | Service account secret which has admins api access    | yes        | -      |
+| `TEST_DE_EDITOR_OPENID_PROVIDER_CLIENT_ID`     | `********`                              | Service account client id which has editor api access | yes        | -      |
+| `TEST_DE_EDITOR_OPENID_PROVIDER_CLIENT_SECRET` | `********`                              | Service account secret which has editor api access    | yes        | -      |
+| `TEST_DE_OPS_OPENID_PROVIDER_CLIENT_ID`        | `********`                              | Service account client id which has full api access   | yes        | -      |
+| `TEST_DE_OPS_OPENID_PROVIDER_CLIENT_SECRET`    | `********`                              | Service account secret which has full api access      | yes        | -      |
+| `TEST_NO_ACCESS_OPENID_PROVIDER_CLIENT_ID`     | `********`                              | Service account client id which has not api access    | yes        | -      |
+| `TEST_NO_ACCESS_OPENID_PROVIDER_CLIENT_SECRET` | `********`                              | Service account secret which has not api access       | yes        | -      |
+| `TEST_OPENID_PROVIDER_URL`                     | `https://keycloak.com/auth/realms/osdu` | OpenID provider url                                   | yes        | -      |
+
+Or tokens can be used directly from env variables:
+
+| name                   | value      | description          | sensitive? | source |
+|------------------------|------------|----------------------|------------|--------|
+| `DE_ADMIN_USER_TOKEN`  | `********` | DE_ADMIN_USER Token  | yes        | -      |
+| `DE_EDITOR_USER_TOKEN` | `********` | DE_EDITOR_USER Token | yes        | -      |
+| `DE_OPS_USER_TOKEN`    | `********` | DE_OPS_USER Token    | yes        | -      |
+| `NO_ACCESS_USER_TOKEN` | `********` | NO_ACCESS_USER Token | yes        | -      |
+
+**Entitlements configuration for integration accounts**
+
+| DE_OPS_TESTER                                                                           | DE_ADMIN_TESTER                                                                       | DE_EDITOR_TESTER                                                    | DE_NO_ACCESS_TESTER                      |
+|-----------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|---------------------------------------------------------------------|------------------------------------------|
+| notification.pubsub<br/>service.entitlements.user<br/>users<br/>users.datalake.ops</br> | service.entitlements.user<br/>users<br/>users.datalake.admins</br>service.legal.admin | service.entitlements.user<br/>users<br/>users.datalake.editors</br> | service.entitlements.user<br/>users<br/> |
+
+Above variables should be configured in the release pipeline to run integration tests. You should also replace them with proper values if you wish to run tests locally.
+
+### Commands to run tests
+Execute following command to build code and run all the integration tests:
+
+ ```bash
+ # Note: this assumes that the environment variables for acceptance tests as outlined
+ #       above are already exported in your environment.
+ # build + install integration test core
+ $ (cd notification-acceptance-test && mvn clean verify)
+ ```
+
+## License
+
+Copyright © Google LLC
+
+Copyright © EPAM Systems
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/notification-acceptance-test/pom.xml b/notification-acceptance-test/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bd3ee4ff18ac245e10da466da35e0fdea4903072
--- /dev/null
+++ b/notification-acceptance-test/pom.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2017-2020, Schlumberger
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.opengroup.osdu.notification</groupId>
+    <artifactId>notification-acceptance-test</artifactId>
+    <version>0.28.0-SNAPSHOT</version>
+    <name>notification-acceptance-test</name>
+
+    <parent>
+        <groupId>org.opengroup.osdu</groupId>
+        <artifactId>os-notification</artifactId>
+        <version>0.28.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <properties>
+        <java.version>17</java.version>
+        <maven.compiler.target>${java.version}</maven.compiler.target>
+        <maven.compiler.source>${java.version}</maven.compiler.source>
+        <jackson-databind.version>2.15.2</jackson-databind.version>
+        <jackson.version>2.15.2</jackson.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.opengroup.osdu</groupId>
+            <artifactId>os-core-common</artifactId>
+            <version>0.25.0-rc2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.26</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>5.10.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-console</artifactId>
+            <version>1.10.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>oauth2-oidc-sdk</artifactId>
+            <version>9.15</version>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.xml.bind</groupId>
+            <artifactId>jakarta.xml.bind-api</artifactId>
+            <version>4.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.xml.bind</groupId>
+            <artifactId>jaxb-core</artifactId>
+            <version>4.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.xml.bind</groupId>
+            <artifactId>jaxb-impl</artifactId>
+            <version>4.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.opengroup.osdu</groupId>
+            <artifactId>os-core-common</artifactId>
+            <version>0.25.0-rc2</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>log4j-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>javax.json</groupId>
+            <artifactId>javax.json-api</artifactId>
+            <version>1.1.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${jackson-databind.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish</groupId>
+            <artifactId>javax.json</artifactId>
+            <version>1.1.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.oauth-client</groupId>
+            <artifactId>google-oauth-client</artifactId>
+            <version>1.34.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.api-client</groupId>
+            <artifactId>google-api-client</artifactId>
+            <version>2.2.0</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.guava</groupId>
+                    <artifactId>guava-jdk5</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-client</artifactId>
+            <version>1.19.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.10.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.auth</groupId>
+            <artifactId>google-auth-library-oauth2-http</artifactId>
+            <version>0.19.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.13.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.22</version>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>3.0.0</version>
+                <configuration>
+                    <systemPropertyVariables>
+                        <DE_OPS_TESTER>${DE_OPS_TESTER}</DE_OPS_TESTER>
+                        <DE_ADMIN_TESTER>${DE_ADMIN_TESTER}</DE_ADMIN_TESTER>
+                        <DE_EDITOR_TESTER>${DE_EDITOR_TESTER}</DE_EDITOR_TESTER>
+                        <DE_NO_ACCESS_TESTER>${DE_NO_ACCESS_TESTER}</DE_NO_ACCESS_TESTER>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/PubsubEndpointGSADescriptor.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/PubsubEndpointGSADescriptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..476c1b684115c2bbf9bfc5706b10fbc4fc2d4e43
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/PubsubEndpointGSADescriptor.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017-2020, Schlumberger
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.api;
+
+import org.opengroup.osdu.notification.util.Config;
+import org.opengroup.osdu.notification.util.RestDescriptor;
+import org.opengroup.osdu.notification.util.TestUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class PubsubEndpointGSADescriptor extends RestDescriptor {
+
+    @Override
+    public String getPath() {
+        return "push-handlers/records-changed";
+    }
+
+    @Override
+    public String getHttpMethod() {
+        return "POST";
+    }
+
+    @Override
+    public String getValidBody() {
+        return "{\n" +
+                "\t\"message\": {\n" +
+                "\t\"attributes\": {\n" +
+                "\t\"correlation-id\": \"39137f49-67d6-4001-a6aa-15521ef4f49e\",\n" +
+                "\t\"data-partition-id\": \"" + TestUtils.getOsduTenant() + "\"},\n" +
+                "\t\"data\": \"W3sia2luZCI6InRlc3RraW5kIiwiaWQiOiJ0ZXN0aWQiLCJvcGVyYXRpb250eXBlIjoiY3JlYXRlIn0seyJraW5kIjoidGVzdGtpbmQyIiwiaWQiOiJ0ZXN0aWQyIiwib3BlcmF0aW9udHlwZSI6InVwZGF0ZSJ9XQ\",\n" +
+                "\t\"messageId\": \"136969346945\"},\n" +
+                "\t\"subscription\":\"" + Config.Instance().NotificationId + "\"\n" +
+                "}";
+    }
+
+    @Override
+    public Map<String,String> getOsduTenantHeaders(){
+        Map<String, String> headers = new HashMap<>();
+        headers.put("data-partition-id", Config.Instance().OsduTenant);
+        return headers;
+    }
+
+    @Override
+    public Map<String,String> getCustomerTenantHeaders(){
+        Map<String, String> headers = new HashMap<>();
+        headers.put("data-partition-id", Config.Instance().ClientTenant);
+        return headers;
+    }
+}
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/PubsubEndpointHMACDescriptor.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/PubsubEndpointHMACDescriptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..60791c283f2088c22626c67c1d79951e2272fa7e
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/PubsubEndpointHMACDescriptor.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017-2020, Schlumberger
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.api;
+
+import org.opengroup.osdu.notification.util.Config;
+import org.opengroup.osdu.notification.util.RestDescriptor;
+import org.opengroup.osdu.notification.util.TestUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class PubsubEndpointHMACDescriptor extends RestDescriptor {
+
+    @Override
+    public String getPath() {
+        return "push-handlers/records-changed";
+    }
+
+    @Override
+    public String getHttpMethod() {
+        return "POST";
+    }
+
+    @Override
+    public String getValidBody() {
+        return  "{\n" +
+                "\t\"message\": {\n" +
+                "\t\"attributes\": {\n" +
+                "\t\"correlation-id\": \"39137f49-67d6-4001-a6aa-15521ef4f49e\",\n" +
+                "\t\"data-partition-id\": \"" + TestUtils.getOsduTenant() + "\"\n" +
+                "\t},\n" +
+                "\t\"data\": \"W3sia2luZCI6InRlc3RraW5kIiwiaWQiOiJ0ZXN0aWQiLCJvcGVyYXRpb250eXBlIjoiY3JlYXRlIn0seyJraW5kIjoidGVzdGtpbmQyIiwiaWQiOiJ0ZXN0aWQyIiwib3BlcmF0aW9udHlwZSI6InVwZGF0ZSJ9XQ\",\n" +
+                "\t\"messageId\": \"136969346945\"\n" +
+                "\t},\n" +
+                "\t\"subscription\": \""+ arg() +"\"\n" +
+                "}\n";
+    }
+
+    @Override
+    public Map<String,String> getOsduTenantHeaders(){
+        Map<String, String> headers = new HashMap<>();
+        headers.put("data-partition-id", Config.Instance().OsduTenant);
+        return headers;
+    }
+
+    @Override
+    public Map<String,String> getCustomerTenantHeaders(){
+        Map<String, String> headers = new HashMap<>();
+        headers.put("data-partition-id", Config.Instance().ClientTenant);
+        return headers;
+    }
+}
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/TestPushEndpointGsa.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/TestPushEndpointGsa.java
new file mode 100644
index 0000000000000000000000000000000000000000..bfd04c2be94c4b6e26ab890427be0ff6ecfd0244
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/TestPushEndpointGsa.java
@@ -0,0 +1,171 @@
+/*
+ *  Copyright 2020-2023 Google LLC
+ *  Copyright 2020-2023 EPAM Systems, Inc
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.api;
+
+
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import static org.opengroup.osdu.notification.util.Constants.GROUP_ID;
+
+import com.google.common.base.Strings;
+import com.sun.jersey.api.client.ClientResponse;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.opengroup.osdu.core.common.model.http.DpsHeaders;
+import org.opengroup.osdu.core.common.model.notification.GsaSecret;
+import org.opengroup.osdu.core.common.model.notification.GsaSecretValue;
+import org.opengroup.osdu.core.common.model.notification.Secret;
+import org.opengroup.osdu.core.common.model.notification.Subscription;
+import org.opengroup.osdu.core.common.notification.ISubscriptionService;
+import org.opengroup.osdu.core.common.notification.SubscriptionAPIConfig;
+import org.opengroup.osdu.core.common.notification.SubscriptionException;
+import org.opengroup.osdu.core.common.notification.SubscriptionFactory;
+import org.opengroup.osdu.notification.util.*;
+
+public class TestPushEndpointGsa{
+  public static final String REGISTER_BASE_URL = "REGISTER_BASE_URL";
+  public static final String TOPIC_ID = "TOPIC_ID";
+  public static final String INTEGRATION_AUDIENCE = "INTEGRATION_AUDIENCE";
+  public static final String OSDU_TENANT = "OSDU_TENANT";
+  public static final String STORAGE_HOST = "STORAGE_HOST";
+  public static final String LEGAL_HOST = "LEGAL_HOST";
+  public static final String DE_OPS_TESTER = "DE_OPS_TESTER";
+  private final static Predicate<String> contentAcceptanceTester = s -> s.trim().startsWith("{");
+  private String subscriptionId = null;
+  private String notificationId = null;
+  private static ISubscriptionService subscriptionService;
+  private static SubscriptionFactory factory;
+  private TestUtils testUtils = new TokenTestUtils();
+  private final String suffix = String.valueOf(System.currentTimeMillis());
+  private String baseRegisterUrl;
+  private String topic;
+  private String tenant;
+  private String integrationAudience;
+  private String storageHost;
+  private String legalHost;
+  private String groupId;
+  private static final String LEGAL_TAG_NAME = "notification-test-gsa";
+  private ServicesUtils servicesUtils;
+
+  @BeforeAll
+  public static void classSetup() throws Exception {
+    // Check if GSA tests are enabled
+    assumeTrue(Config.Instance().GSATestIsEnabled);
+  }
+
+  @AfterEach
+  public void deleteResource() throws Exception {
+    if (Objects.isNull(subscriptionService)) {
+      Map<String, String> headers = new HashMap<>();
+      headers.put(DpsHeaders.DATA_PARTITION_ID, System.getProperty(OSDU_TENANT, System.getenv(OSDU_TENANT)));
+      headers.put(DpsHeaders.AUTHORIZATION, testUtils.getOpsToken());
+      DpsHeaders dpsHeaders = DpsHeaders.createFromMap(headers);
+      subscriptionService = factory.create(dpsHeaders);
+    }
+
+    subscriptionService.delete(subscriptionId);
+    servicesUtils.deleteStorageRecords(3, suffix);
+    servicesUtils.deleteLegalTag(LEGAL_TAG_NAME);
+  }
+
+  @BeforeEach
+  public void createResource() throws Exception {
+    baseRegisterUrl = System.getProperty(REGISTER_BASE_URL, System.getenv(REGISTER_BASE_URL));
+    topic = System.getProperty(TOPIC_ID, System.getenv(TOPIC_ID));
+    integrationAudience = System.getProperty(INTEGRATION_AUDIENCE, System.getenv(INTEGRATION_AUDIENCE));
+    tenant = System.getProperty(OSDU_TENANT, System.getenv(OSDU_TENANT));
+    if (Strings.isNullOrEmpty(integrationAudience)) {
+      integrationAudience = tenant;
+    }
+    storageHost = System.getProperty(STORAGE_HOST, System.getenv(STORAGE_HOST));
+    legalHost = System.getProperty(LEGAL_HOST, System.getenv(LEGAL_HOST));
+    groupId = System.getProperty(GROUP_ID, System.getenv(GROUP_ID));
+    servicesUtils = new ServicesUtils(storageHost, legalHost, testUtils, tenant, groupId);
+    servicesUtils.createLegalTag(LEGAL_TAG_NAME);
+    createResourceInPartition(tenant);
+  }
+
+  @Test
+  public void testPushEndpoint() throws Exception {
+    servicesUtils.createStorageRecords(suffix, 3, LEGAL_TAG_NAME);
+    Thread.sleep(10000);
+    assertNotNull(subscriptionId);
+    assertNotNull(notificationId);
+    Map<String, String> headers = new HashMap<>();
+    headers.put(DpsHeaders.DATA_PARTITION_ID, tenant);
+    ClientResponse clientResponse = testUtils.send(baseRegisterUrl, "/test-state/state", "GET", testUtils.getOpsToken(), null, "",
+        headers, false);
+    Map<String, Number> response = new HashMap<>();
+    response = testUtils.getResult(clientResponse, 200, response.getClass());
+    assertNotNull(response);
+    assertTrue(response.containsKey(suffix));
+    assertTrue(response.get(suffix).longValue() >= 3);
+  }
+
+  private void createResourceInPartition(String partitionId) throws Exception {
+
+    SubscriptionAPIConfig config = SubscriptionAPIConfig.builder().rootUrl(baseRegisterUrl).build();
+    factory = new SubscriptionFactory(config);
+
+    Map<String, String> headers = new HashMap<>();
+    headers.put(DpsHeaders.DATA_PARTITION_ID, partitionId);
+    headers.put(DpsHeaders.AUTHORIZATION, testUtils.getOpsToken());
+    DpsHeaders dpsHeaders = DpsHeaders.createFromMap(headers);
+    subscriptionService = factory.create(dpsHeaders);
+
+    Subscription subscription = new Subscription();
+    subscription.setName("subscription-integration-test-gsa-" + suffix);
+    subscription.setDescription("subscription created for gsa integration test " + suffix);
+    subscription.setTopic(topic);
+    subscription.setPushEndpoint(getPushUrl());
+
+    Secret gsaSecret = new GsaSecret();
+    GsaSecretValue gsaSecretValue = new GsaSecretValue();
+    gsaSecretValue.setAudience(integrationAudience);
+
+    String opsTester = new DecodedContentExtractor(System.getProperty(DE_OPS_TESTER, System.getenv(DE_OPS_TESTER)),
+            contentAcceptanceTester).getContent();
+
+    gsaSecretValue.setKey(opsTester);
+    gsaSecret.setSecretType("GSA");
+    ((GsaSecret) gsaSecret).setValue(gsaSecretValue);
+    subscription.setSecret(gsaSecret);
+    try {
+      Subscription subscriptionCreated = subscriptionService.create(subscription);
+      notificationId = subscriptionCreated.getNotificationId();
+      subscriptionId = subscriptionCreated.getId();
+    } catch (SubscriptionException e) {
+      System.out.println("Subscription exception inner response : " + e.getHttpResponse());
+      throw e;
+    }
+  }
+
+  private String getPushUrl() {
+    return baseRegisterUrl + "/test-state/gsa-challenge/" + suffix;
+  }
+}
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/TestPushEndpointHMAC.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/TestPushEndpointHMAC.java
new file mode 100644
index 0000000000000000000000000000000000000000..2bf0f37da6f5e5908ca4432960981461666545f8
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/api/TestPushEndpointHMAC.java
@@ -0,0 +1,141 @@
+/*
+ *  Copyright 2020-2023 Google LLC
+ *  Copyright 2020-2023 EPAM Systems, Inc
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.api;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.opengroup.osdu.notification.util.Constants.GROUP_ID;
+
+import com.sun.jersey.api.client.ClientResponse;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opengroup.osdu.core.common.model.http.DpsHeaders;
+import org.opengroup.osdu.core.common.model.notification.HmacSecret;
+import org.opengroup.osdu.core.common.model.notification.Subscription;
+import org.opengroup.osdu.core.common.notification.ISubscriptionService;
+import org.opengroup.osdu.core.common.notification.SubscriptionAPIConfig;
+import org.opengroup.osdu.core.common.notification.SubscriptionException;
+import org.opengroup.osdu.core.common.notification.SubscriptionFactory;
+import org.opengroup.osdu.notification.util.*;
+
+public class TestPushEndpointHMAC{
+  public static final String REGISTER_BASE_URL = "REGISTER_BASE_URL";
+  public static final String TOPIC_ID = "TOPIC_ID";
+  public static final String HMAC_SECRET = "HMAC_SECRET";
+  public static final String OSDU_TENANT = "OSDU_TENANT";
+  public static final String STORAGE_HOST = "STORAGE_HOST";
+  public static final String LEGAL_HOST = "LEGAL_HOST";
+  private String subscriptionId = null;
+  private String notificationId = null;
+  private ISubscriptionService subscriptionService;
+  private static SubscriptionFactory factory;
+  private TestUtils testUtils = new TokenTestUtils();
+  private final String suffix = String.valueOf(System.currentTimeMillis());
+  private String baseRegisterUrl;
+  private String topic;
+  private String hmacSecretValue;
+  private String tenant;
+  private String storageHost;
+  private String legalHost;
+  private String groupId;
+  private static final String LEGAL_TAG_NAME = "notification-test-hmac";
+  private ServicesUtils servicesUtils;
+
+  @AfterEach
+  public void deleteResource() throws Exception {
+    if (Objects.isNull(subscriptionService)) {
+      Map<String, String> headers = new HashMap<>();
+      headers.put(DpsHeaders.DATA_PARTITION_ID, System.getProperty(OSDU_TENANT, System.getenv(OSDU_TENANT)));
+      headers.put(DpsHeaders.AUTHORIZATION, testUtils.getOpsToken());
+      DpsHeaders dpsHeaders = DpsHeaders.createFromMap(headers);
+      subscriptionService = factory.create(dpsHeaders);
+    }
+
+    subscriptionService.delete(subscriptionId);
+    servicesUtils.deleteStorageRecords(3, suffix);
+    servicesUtils.deleteLegalTag(LEGAL_TAG_NAME);
+  }
+
+  @BeforeEach
+  public void createResource() throws Exception {
+    baseRegisterUrl = System.getProperty(REGISTER_BASE_URL, System.getenv(REGISTER_BASE_URL));
+    topic = System.getProperty(TOPIC_ID, System.getenv(TOPIC_ID));
+    hmacSecretValue = System.getProperty(HMAC_SECRET, System.getenv(HMAC_SECRET));
+    tenant = System.getProperty(OSDU_TENANT, System.getenv(OSDU_TENANT));
+    storageHost = System.getProperty(STORAGE_HOST, System.getenv(STORAGE_HOST));
+    legalHost = System.getProperty(LEGAL_HOST, System.getenv(LEGAL_HOST));
+    groupId = System.getProperty(GROUP_ID, System.getenv(GROUP_ID));
+    servicesUtils = new ServicesUtils(storageHost, legalHost, testUtils, tenant, groupId);
+    servicesUtils.createLegalTag(LEGAL_TAG_NAME);
+    createResourceInPartition(tenant);
+  }
+
+  @Test
+  public void testPushEndpoint() throws Exception {
+    servicesUtils.createStorageRecords(suffix, 3, LEGAL_TAG_NAME);
+    Thread.sleep(10000);
+    assertNotNull(subscriptionId);
+    assertNotNull(notificationId);
+    Map<String, String> headers = new HashMap<>();
+    headers.put(DpsHeaders.DATA_PARTITION_ID, tenant);
+    ClientResponse clientResponse = testUtils.send(baseRegisterUrl, "/test-state/state", "GET", testUtils.getOpsToken(), null, "",
+        headers, false);
+    Map<String, Number> response = new HashMap<>();
+    response = testUtils.getResult(clientResponse, 200, response.getClass());
+    assertNotNull(response);
+    assertTrue(response.containsKey(suffix));
+    assertTrue(response.get(suffix).longValue() >= 3);
+  }
+
+  private void createResourceInPartition(String partitionId) throws Exception {
+
+    SubscriptionAPIConfig config = SubscriptionAPIConfig.builder().rootUrl(baseRegisterUrl).build();
+    factory = new SubscriptionFactory(config);
+
+    Map<String, String> headers = new HashMap<>();
+    headers.put(DpsHeaders.DATA_PARTITION_ID, partitionId);
+    headers.put(DpsHeaders.AUTHORIZATION, testUtils.getOpsToken());
+    DpsHeaders dpsHeaders = DpsHeaders.createFromMap(headers);
+    subscriptionService = factory.create(dpsHeaders);
+
+    Subscription subscription = new Subscription();
+    subscription.setName("subscription-integration-test-hmac-" + suffix);
+    subscription.setDescription("subscription created for hmac integration test " + suffix);
+    subscription.setTopic(topic);
+    subscription.setPushEndpoint(getPushUrl());
+    HmacSecret secret = new HmacSecret();
+    secret.setValue(hmacSecretValue);
+
+    subscription.setSecret(secret);
+    try {
+      Subscription subscriptionCreated = subscriptionService.create(subscription);
+      notificationId = subscriptionCreated.getNotificationId();
+      subscriptionId = subscriptionCreated.getId();
+    } catch (SubscriptionException e) {
+      System.out.println("Subscription exception inner response : " + e.getHttpResponse());
+      throw e;
+    }
+  }
+  private String getPushUrl() {
+    return baseRegisterUrl + "/test-state/challenge/" + suffix;
+  }
+}
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/config/OpenIDProviderConfig.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/config/OpenIDProviderConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..58994bd3e7f5f09d1ed8c7ccb604c3d33da70e99
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/config/OpenIDProviderConfig.java
@@ -0,0 +1,129 @@
+/*
+  Copyright 2002-2022 Google LLC
+  Copyright 2002-2022 EPAM Systems, Inc
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.config;
+
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderConfigurationRequest;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class OpenIDProviderConfig {
+
+  private String opsClientId;
+  private String opsClientSecret;
+  private String adminClientId;
+  private String adminClientSecret;
+  private String editorClientId;
+  private String editorClientSecret;
+  private String noAccessClientId;
+  private String noAccessClientSecret;
+  private String url;
+  private final String[] scopes = {"openid"};
+  private static final OpenIDProviderConfig openIDProviderConfig = new OpenIDProviderConfig();
+  private static OIDCProviderMetadata providerMetadata;
+
+  public static OpenIDProviderConfig Instance() {
+    try {
+      openIDProviderConfig.opsClientId = System.getProperty("TEST_DE_OPS_OPENID_PROVIDER_CLIENT_ID",
+          System.getenv("TEST_DE_OPS_OPENID_PROVIDER_CLIENT_ID"));
+      openIDProviderConfig.opsClientSecret = System.getProperty(
+          "TEST_DE_OPS_OPENID_PROVIDER_CLIENT_SECRET",
+          System.getenv("TEST_DE_OPS_OPENID_PROVIDER_CLIENT_SECRET"));
+
+      openIDProviderConfig.adminClientId = System.getProperty(
+          "TEST_DE_ADMIN_OPENID_PROVIDER_CLIENT_ID",
+          System.getenv("TEST_DE_ADMIN_OPENID_PROVIDER_CLIENT_ID"));
+      openIDProviderConfig.adminClientSecret = System.getProperty(
+          "TEST_DE_ADMIN_OPENID_PROVIDER_CLIENT_SECRET",
+          System.getenv("TEST_DE_ADMIN_OPENID_PROVIDER_CLIENT_SECRET"));
+
+      openIDProviderConfig.editorClientId = System.getProperty(
+          "TEST_DE_EDITOR_OPENID_PROVIDER_CLIENT_ID",
+          System.getenv("TEST_DE_EDITOR_OPENID_PROVIDER_CLIENT_ID"));
+      openIDProviderConfig.editorClientSecret = System.getProperty(
+          "TEST_DE_EDITOR_OPENID_PROVIDER_CLIENT_SECRET",
+          System.getenv("TEST_DE_EDITOR_OPENID_PROVIDER_CLIENT_SECRET"));
+
+      openIDProviderConfig.noAccessClientId = System.getProperty(
+          "TEST_NO_ACCESS_OPENID_PROVIDER_CLIENT_ID",
+          System.getenv("TEST_NO_ACCESS_OPENID_PROVIDER_CLIENT_ID"));
+      openIDProviderConfig.noAccessClientSecret = System.getProperty(
+          "TEST_NO_ACCESS_OPENID_PROVIDER_CLIENT_SECRET",
+          System.getenv("TEST_NO_ACCESS_OPENID_PROVIDER_CLIENT_SECRET"));
+
+      openIDProviderConfig.url =
+          System.getProperty("TEST_OPENID_PROVIDER_URL", System.getenv("TEST_OPENID_PROVIDER_URL"));
+      Issuer issuer = new Issuer(openIDProviderConfig.url);
+      OIDCProviderConfigurationRequest request = new OIDCProviderConfigurationRequest(issuer);
+      HTTPRequest httpRequest = request.toHTTPRequest();
+      HTTPResponse httpResponse = httpRequest.send();
+      providerMetadata = OIDCProviderMetadata.parse(httpResponse.getContentAsJSONObject());
+    } catch (Exception e) {
+      throw new RuntimeException("Malformed token provider configuration", e);
+    }
+    return openIDProviderConfig;
+  }
+
+  public String getOpsClientId() {
+    return opsClientId;
+  }
+
+  public String getOpsClientSecret() {
+    return opsClientSecret;
+  }
+
+  public String getAdminClientId() {
+    return adminClientId;
+  }
+
+  public String getAdminClientSecret() {
+    return adminClientSecret;
+  }
+
+  public String getEditorClientId() {
+    return editorClientId;
+  }
+
+  public String getEditorClientSecret() {
+    return editorClientSecret;
+  }
+
+  public String getNoAccessClientId() {
+    return noAccessClientId;
+  }
+
+  public String getNoAccessClientSecret() {
+    return noAccessClientSecret;
+  }
+
+  public String getUrl() {
+    return url;
+  }
+
+  public String[] getScopes() {
+    return scopes;
+  }
+
+  public OIDCProviderMetadata getProviderMetadata() {
+    return providerMetadata;
+  }
+}
+
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/Config.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/Config.java
new file mode 100644
index 0000000000000000000000000000000000000000..5c3e0a02670b204b21c6ee0358840c0fe90003f6
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/Config.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017-2020, Schlumberger
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.util;
+
+public class Config {
+    public String HostUrl;
+    public String OsduTenant;
+    public String ClientTenant;
+    public String IntegrationAudience;
+    public String GSAPushUrl;
+    public String HMACPushUrl;
+    public String RegisterServicePath;
+    public String StorageServicePath;
+    public String LegalServicePath;
+    public String Topic;
+    public String hmacSecretValue;
+    public String NotificationId;
+    public String DE_OPS_TESTER = System.getProperty("DE_OPS_TESTER", System.getenv("DE_OPS_TESTER"));
+    public String RetryCount;
+    public String TimeOutSeconds;
+    public Boolean GSATestIsEnabled;
+    private static Config config = new Config();
+
+    public static Config Instance() {
+        String env = getEnvironment();
+        config.ClientTenant = getEnvironmentVariableOrDefaultValue("CLIENT_TENANT", "nonexistenttenant");
+        config.IntegrationAudience = "osdu";
+        config.OsduTenant = getEnvironmentVariableOrDefaultValue("OSDU_TENANT", "opendes");
+        config.Topic = getEnvironmentVariableOrDefaultValue("TOPIC_ID", "records-changed");
+        config.TimeOutSeconds = getEnvironmentVariableOrDefaultValue("TIME_OUT_SECONDS", "60");
+        config.RetryCount = getEnvironmentVariableOrDefaultValue("RETRY_COUNT", "3");
+        config.hmacSecretValue = System.getProperty("HMAC_SECRET", System.getenv("HMAC_SECRET"));
+        config.GSATestIsEnabled = Boolean.parseBoolean(getEnvironmentVariableOrDefaultValue("GSA_TEST_ENABLED", "false").trim().toLowerCase());
+
+        String registerUrl = System.getProperty("REGISTER_BASE_URL", System.getenv("REGISTER_BASE_URL"));
+        config.HostUrl = System.getProperty("NOTIFICATION_BASE_URL", System.getenv("NOTIFICATION_BASE_URL"));
+        config.GSAPushUrl = registerUrl + "/test/gsa-challenge/";
+        //Adding this so CPs can point to custom HMAC push endpoints
+        config.HMACPushUrl = getEnvironmentVariableOrDefaultValue("REGISTER_CUSTOM_PUSH_URL_HMAC", registerUrl + "/test/challenge/");
+        //Adding a new variable NOTIFICATION_REGISTER_BASE_URL since REGISTER_BASE_URL is used by Register integration tests which needs a trailing \
+        String regUrl = getEnvironmentVariable("NOTIFICATION_REGISTER_BASE_URL");
+        config.StorageServicePath = getEnvironmentVariable("STORAGE_HOST");
+        config.LegalServicePath = System.getProperty("LEGAL_URL", System.getenv("LEGAL_URL"));
+        if (regUrl == null) {
+            config.RegisterServicePath = registerUrl;
+        } else {
+            config.RegisterServicePath = regUrl + "/api/register/v1";
+        }
+
+        System.out.println("HostUrl=" + config.HostUrl);
+        System.out.println("config.Topic=" + config.Topic);
+        System.out.println("config.HMACPushUrl=" + config.HMACPushUrl);
+        System.out.println("config.RegisterServicePath=" + config.RegisterServicePath);
+        System.out.println("config.StorageServicePath=" + config.StorageServicePath);
+        return config;
+    }
+
+    public boolean isLocalHost() {
+        return HostUrl.contains("//localhost");
+    }
+
+    public static boolean isGke() {
+        return "DEV_GKE".equalsIgnoreCase(getEnvironment());
+    }
+
+    private static String getEnvironment() {
+        return System.getProperty("ENVIRONMENT", System.getenv("ENVIRONMENT"));
+    }
+
+    private static String getEnvironmentVariableOrDefaultValue(String key, String defaultValue) {
+        String environmentVariable = getEnvironmentVariable(key);
+        if (environmentVariable == null) {
+            environmentVariable = defaultValue;
+        }
+        return environmentVariable;
+    }
+
+    private static String getEnvironmentVariable(String propertyKey) {
+        return System.getProperty(propertyKey, System.getenv(propertyKey));
+    }
+}
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/Constants.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/Constants.java
new file mode 100644
index 0000000000000000000000000000000000000000..7889fab81ba31cbd3f5c840c2aca9d838c02aa21
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/Constants.java
@@ -0,0 +1,6 @@
+package org.opengroup.osdu.notification.util;
+
+public class Constants {
+
+  public static final String GROUP_ID = "GROUP_ID";
+}
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/DecodedContentExtractor.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/DecodedContentExtractor.java
new file mode 100644
index 0000000000000000000000000000000000000000..106e582fb90cd7c4fc249de76600cd84653d795f
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/DecodedContentExtractor.java
@@ -0,0 +1,126 @@
+/*
+ *  Copyright 2020-2024 Google LLC
+ *  Copyright 2020-2024 EPAM Systems, Inc
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.util;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Base64;
+import java.util.function.Predicate;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.java.Log;
+
+@Log
+@RequiredArgsConstructor
+public class DecodedContentExtractor {
+    private final String inputFilenameOrContent;
+    private final Predicate<String> contentAcceptanceTester;
+    private boolean validOutputContentFound;
+    private String outputContent;
+
+    public String getContent() {
+
+        validOutputContentFound = false;
+        outputContent = null;
+
+        log.info("Treat value as a content");
+        if (inputFilenameOrContent.trim().isEmpty()) {
+            log.info("provided value is empty. Output as is.");
+            return setValidOutputContent(inputFilenameOrContent);
+        }
+
+        if (!treatValueAsAContent(inputFilenameOrContent)) {
+            log.info("Value is not a valid content. Treat value as a filename");
+            if (!treatValueAsAFileName(inputFilenameOrContent)){
+                log.info("Value is not a filename with a valid content");
+            }
+
+        }
+
+        return getValidOutputContentIfFound();
+    }
+
+    private boolean treatValueAsAContent(String input) {
+        if (contentAcceptanceTester.test(input)) {
+            log.info("the value is a valid content. Output as is.");
+            setValidOutputContent(input);
+            return true;
+        }
+        String output;
+        try {
+            output = new String(Base64.getDecoder().decode(input));
+            log.info("the value is probably Base64 encoded. Just decoded");
+            if (contentAcceptanceTester.test(output)) {
+                log.info("the decoded value is a valid content. Output decoded value.");
+                setValidOutputContent(output);
+            } else {
+                log.info("the decoded value is not a valid content.");
+            }
+        } catch (IllegalArgumentException e) {
+            log.info("the value is not Base64 encoded. ");
+        }
+
+        return validOutputContentFound;
+    }
+
+    private boolean treatValueAsAFileName(String filename) {
+
+        if (treatFileContent(filename)) return true;
+
+        try {
+            filename = new String(Base64.getDecoder().decode(filename));
+            log.info("the filename is probably Base64 encoded. Just decoded");
+            if (treatFileContent(filename)) return true;
+        } catch (IllegalArgumentException e) {
+            log.info("the filename is not Base64 encoded. ");
+        }
+        return validOutputContentFound;
+    }
+
+    private boolean treatFileContent(String filename) {
+        try {
+            Path path = Paths.get(filename);
+            if (Files.exists(path)) {
+                log.info("the filename is of existing file. Read file.");
+                try {
+                    String fileContent = new String(Files.readAllBytes(path));
+                    if (treatValueAsAContent(fileContent)) {
+                        return true;
+                    }
+                } catch (IOException | SecurityException | OutOfMemoryError ex) {
+                    log.info(() -> ("unable to read the file: " + ex.getClass().getSimpleName()));
+                }
+            }
+        } catch (InvalidPathException ex) {
+            log.info("the filename is not valid or the file doesn't exist.");
+        }
+        return false;
+    }
+
+    private String setValidOutputContent(String outputContent) {
+        this.outputContent = outputContent;
+        this.validOutputContentFound = true;
+        return getValidOutputContentIfFound();
+    }
+
+    public String getValidOutputContentIfFound() {
+        return validOutputContentFound ? outputContent : null;
+    }
+}
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/FileUtils.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/FileUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3af459967382cd0600f8f6a51a6a2135641cda8
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/FileUtils.java
@@ -0,0 +1,38 @@
+/*
+ *  Copyright 2020-2023 Google LLC
+ *  Copyright 2020-2023 EPAM Systems, Inc
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class FileUtils {
+  public String readFromLocalFilePath(String filePath) throws IOException {
+    InputStream inStream = this.getClass().getResourceAsStream(filePath);
+    BufferedReader br = new BufferedReader(new InputStreamReader(inStream));
+    StringBuilder stringBuilder = new StringBuilder();
+
+    String eachLine = "";
+    while ((eachLine = br.readLine()) != null) {
+      stringBuilder.append(eachLine);
+    }
+
+    return stringBuilder.toString();
+  }
+}
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/HttpClient.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/HttpClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..1df1321be243b60b237a710b1d03523e67e25ca6
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/HttpClient.java
@@ -0,0 +1,108 @@
+/*
+ *  Copyright 2020-2023 Google LLC
+ *  Copyright 2020-2023 EPAM Systems, Inc
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.google.gson.Gson;
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+import java.net.URI;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.ws.rs.core.MediaType;
+import org.apache.commons.lang3.StringUtils;
+
+public class HttpClient {
+  private HttpClient(){
+  }
+
+  public static ClientResponse send(String url, String path, String httpMethod,
+                                    Map<String, String> headers,
+                                    String requestBody, String query) throws Exception {
+
+    String normalizedUrl = new URI(String.format("%s/%s", url, path)).normalize().toString();
+    normalizedUrl = StringUtils.removeEnd(normalizedUrl, "/");
+    log(httpMethod, normalizedUrl + query, headers, requestBody);
+    Client client = getClient();
+
+    WebResource webResource = client.resource(normalizedUrl + query);
+    WebResource.Builder builder = webResource.accept(MediaType.APPLICATION_JSON)
+        .type(MediaType.APPLICATION_JSON);
+    headers.forEach(builder::header);
+
+    if ("POST".equals(httpMethod) && StringUtils.isEmpty(requestBody)) {
+      requestBody = "{}"; //solves 411 error when sending empty-body POST request
+    }
+
+    return builder.method(httpMethod, ClientResponse.class, requestBody);
+  }
+
+  private static void log(String method, String url, Map<String, String> headers, String body) {
+    System.out.println(String.format("%s: %s", method, url));
+    System.out.println(body);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <T> T getResult(ClientResponse response, int exepectedStatus, Class<T> classOfT) {
+    assertEquals(exepectedStatus, response.getStatus());
+    if (exepectedStatus == 204) {
+      return null;
+    }
+
+    assertEquals("application/json; charset=UTF-8", response.getType().toString());
+    String json = response.getEntity(String.class);
+    if (classOfT == String.class) {
+      return (T) json;
+    }
+
+    Gson gson = new Gson();
+    return gson.fromJson(json, classOfT);
+  }
+
+  protected static Client getClient() {
+    TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
+      @Override
+      public X509Certificate[] getAcceptedIssuers() {
+        return null;
+      }
+
+      @Override
+      public void checkClientTrusted(X509Certificate[] certs, String authType) {
+      }
+
+      @Override
+      public void checkServerTrusted(X509Certificate[] certs, String authType) {
+      }
+    }};
+
+    try {
+      SSLContext sc = SSLContext.getInstance("TLS");
+      sc.init(null, trustAllCerts, new SecureRandom());
+      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+    } catch (Exception e) {
+    }
+    return Client.create();
+  }
+}
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/OpenIDTokenProvider.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/OpenIDTokenProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b5d220ae7616453c0033f1c5f3cb8cb2b0a4d59
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/OpenIDTokenProvider.java
@@ -0,0 +1,138 @@
+/*
+  Copyright 2002-2022 Google LLC
+  Copyright 2002-2022 EPAM Systems, Inc
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.util;
+
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.ClientCredentialsGrant;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Objects;
+import net.minidev.json.JSONObject;
+import org.opengroup.osdu.notification.config.OpenIDProviderConfig;
+
+public class OpenIDTokenProvider {
+
+  private static final OpenIDProviderConfig openIDProviderConfig = OpenIDProviderConfig.Instance();
+  private static final String ID_TOKEN = "id_token";
+  private final AuthorizationGrant clientGrant = new ClientCredentialsGrant();
+  private final URI tokenEndpointURI;
+  private final Scope scope;
+  private final ClientAuthentication opsClientAuthentication;
+  private final ClientAuthentication adminClientAuthentication;
+  private final ClientAuthentication editorClientAuthentication;
+  private final ClientAuthentication noAccessClientAuthentication;
+
+  public OpenIDTokenProvider() {
+    this.tokenEndpointURI = openIDProviderConfig.getProviderMetadata().getTokenEndpointURI();
+    this.scope = new Scope(openIDProviderConfig.getScopes());
+    this.opsClientAuthentication =
+        new ClientSecretBasic(
+            new ClientID(openIDProviderConfig.getOpsClientId()),
+            new Secret(openIDProviderConfig.getOpsClientSecret())
+        );
+    this.adminClientAuthentication =
+        new ClientSecretBasic(
+            new ClientID(openIDProviderConfig.getAdminClientId()),
+            new Secret(openIDProviderConfig.getAdminClientSecret())
+        );
+    this.editorClientAuthentication =
+        new ClientSecretBasic(
+            new ClientID(openIDProviderConfig.getEditorClientId()),
+            new Secret(openIDProviderConfig.getEditorClientSecret())
+        );
+    this.noAccessClientAuthentication =
+        new ClientSecretBasic(
+            new ClientID(openIDProviderConfig.getNoAccessClientId()),
+            new Secret(openIDProviderConfig.getNoAccessClientSecret())
+        );
+  }
+
+  public String getOpsAccessToken() {
+    try {
+      TokenRequest request =
+          new TokenRequest(this.tokenEndpointURI, this.opsClientAuthentication, this.clientGrant,
+              this.scope);
+      return requestToken(request);
+    } catch (ParseException | IOException e) {
+      throw new RuntimeException(
+          "Unable get credentials from TEST_DE_OPS_OPENID_PROVIDER_CLIENT_ID variables", e);
+    }
+  }
+
+  public String getAdminAccessToken() {
+    try {
+      TokenRequest request =
+          new TokenRequest(this.tokenEndpointURI, this.adminClientAuthentication, this.clientGrant,
+              this.scope);
+      return requestToken(request);
+    } catch (ParseException | IOException e) {
+      throw new RuntimeException(
+          "Unable get credentials from TEST_DE_ADMIN_OPENID_PROVIDER_CLIENT_ID variables", e);
+    }
+  }
+
+  public String getEditorAccessToken() {
+    try {
+      TokenRequest request =
+          new TokenRequest(this.tokenEndpointURI, this.editorClientAuthentication, this.clientGrant,
+              this.scope);
+      return requestToken(request);
+    } catch (ParseException | IOException e) {
+      throw new RuntimeException(
+          "Unable get credentials from TEST_DE_EDITOR_OPENID_PROVIDER_CLIENT_ID variables", e);
+    }
+  }
+
+  public String getNoAccessToken() {
+    try {
+      TokenRequest request =
+          new TokenRequest(this.tokenEndpointURI, this.noAccessClientAuthentication,
+              this.clientGrant, this.scope);
+      return requestToken(request);
+    } catch (ParseException | IOException e) {
+      throw new RuntimeException(
+          "Unable get credentials from TEST_NO_ACCESS_OPENID_PROVIDER_CLIENT_ID variables", e);
+    }
+  }
+
+  private String requestToken(TokenRequest tokenRequest) throws ParseException, IOException {
+
+    TokenResponse parse = OIDCTokenResponseParser.parse(tokenRequest.toHTTPRequest().send());
+
+    if (!parse.indicatesSuccess()) {
+      throw new RuntimeException("Unable get credentials variables");
+    }
+
+    JSONObject jsonObject = parse.toSuccessResponse().toJSONObject();
+    String idTokenValue = jsonObject.getAsString(ID_TOKEN);
+    if (Objects.isNull(idTokenValue) || idTokenValue.isEmpty()) {
+      throw new RuntimeException("Unable get credentials variables");
+    }
+    return idTokenValue;
+  }
+
+}
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/RestDescriptor.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/RestDescriptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..08bab7e551b06c5f172aead358a159d99801257b
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/RestDescriptor.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017-2020, Schlumberger
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.util;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+import java.util.Map;
+
+public abstract class RestDescriptor {
+
+    public RestDescriptor() {
+    }
+
+    private String arg = "";
+
+    public String arg() {
+        return arg;
+    }
+
+    public abstract String getPath();
+
+    public abstract String getHttpMethod();
+
+    public abstract String getValidBody();
+
+    public abstract Map<String, String> getOsduTenantHeaders();
+
+    public abstract Map<String, String> getCustomerTenantHeaders();
+
+    public String getQuery() {
+        return "";
+    }
+
+    public ClientResponse runHttp(String arg, String token) throws Exception {
+        this.arg = arg;
+        return TestUtils.send(getPath(), getHttpMethod(), token, getValidBody(), getQuery(), getOsduTenantHeaders(), true);
+    }
+
+    public ClientResponse run(String arg, String token) throws Exception {
+        this.arg = arg;
+        return TestUtils.send(getPath(), getHttpMethod(), token, getValidBody(), getQuery(), getOsduTenantHeaders(), false);
+    }
+
+    public ClientResponse runOnCustomerTenant(String arg, String token) throws Exception {
+        this.arg = arg;
+        return TestUtils.send(getPath(), getHttpMethod(), token, getValidBody(), getQuery(), getCustomerTenantHeaders(), false);
+    }
+
+    public ClientResponse runOptions(String arg, String token) throws Exception {
+        this.arg = arg;
+        return TestUtils.send(getPath(), "OPTIONS", token, "", "", getOsduTenantHeaders(), false);
+    }
+
+    public ClientResponse run(String url, String arg, String token) throws Exception {
+        this.arg = arg;
+        return TestUtils.send(url, getPath(), getHttpMethod(), token, getValidBody(), getQuery(), getOsduTenantHeaders(), false);
+    }
+}
\ No newline at end of file
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/ServicesUtils.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/ServicesUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..2eaf64b6c072230574047df9ce11c9aa1e450f26
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/ServicesUtils.java
@@ -0,0 +1,96 @@
+/*
+ *  Copyright 2020-2023 Google LLC
+ *  Copyright 2020-2023 EPAM Systems, Inc
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.sun.jersey.api.client.ClientResponse;
+import java.util.HashMap;
+import java.util.Map;
+import org.opengroup.osdu.core.common.model.http.DpsHeaders;
+
+public class ServicesUtils {
+  private String storageHost;
+  private String legalHost;
+  private TestUtils testUtils;
+  private String partitionId;
+  private String groupId;
+  private FileUtils fileUtils;
+
+  public ServicesUtils(String storageHost, String legalHost, TestUtils testUtils, String partitionId, String groupId) {
+    this.storageHost = storageHost;
+    this.legalHost = legalHost;
+    this.testUtils = testUtils;
+    this.partitionId = partitionId;
+    this.groupId = groupId;
+    this.fileUtils = new FileUtils();
+  }
+
+  public ClientResponse createLegalTag(String tagName) throws Exception {
+    String legalBody = fileUtils.readFromLocalFilePath("/LegalTag.json");
+    legalBody = legalBody.replace("{{tagName}}", tagName);
+    Map<String, String> headers = new HashMap<>();
+    headers.put(DpsHeaders.DATA_PARTITION_ID, partitionId);
+    headers.put(DpsHeaders.AUTHORIZATION, testUtils.getAdminToken());
+
+    ClientResponse legalResponse = HttpClient.send(legalHost, "legaltags", "POST", headers, legalBody, "");
+
+    boolean createdOrAlreadyExists = legalResponse.getStatus() == 201 || legalResponse.getStatus() == 409;
+    assertTrue(createdOrAlreadyExists);
+    return legalResponse;
+  }
+
+  public void deleteLegalTag(String tagName) throws Exception{
+    Map<String, String> headers = new HashMap<>();
+    headers.put(DpsHeaders.DATA_PARTITION_ID, partitionId);
+    headers.put(DpsHeaders.AUTHORIZATION, testUtils.getAdminToken());
+    ClientResponse legalResponse = HttpClient.send(legalHost, String.format("legaltags/%s", tagName), "DELETE",
+        headers, "", "");
+    assertEquals(204, legalResponse.getStatus());
+  }
+
+  public void createStorageRecords(String suffix, int count, String legalTag) throws Exception{
+    String body = fileUtils.readFromLocalFilePath("/StorageRecord.json");
+    body = body.replace("{{data-partition-id}}", partitionId);
+    body = body.replace("{{legal-tag}}", partitionId + "-" + legalTag);
+    body = body.replace("{{group_id}}", groupId);
+    for (int i = 0; i < count; i++) {
+      String actualBody = body.replace("{{ids-suffix}}", suffix + String.valueOf(i));
+      Map<String, String> headers = new HashMap<>();
+      headers.put(DpsHeaders.DATA_PARTITION_ID, partitionId);
+      headers.put(DpsHeaders.AUTHORIZATION, testUtils.getAdminToken());
+      ClientResponse storageResponse = HttpClient.send(storageHost, "records", "PUT",
+          headers, actualBody, "");
+      assertEquals(201, storageResponse.getStatus());
+    }
+  }
+
+  public void deleteStorageRecords(int count, String suffix) throws Exception{
+    for (int i = 0; i < count; i++) {
+      Map<String, String> headers = new HashMap<>();
+      headers.put(DpsHeaders.DATA_PARTITION_ID, partitionId);
+      headers.put(DpsHeaders.AUTHORIZATION, testUtils.getAdminToken());
+      String recordId = partitionId + ":dataset--ConnectedSource.Generic:notification-test-" + suffix + i;
+      ClientResponse storageResponse = HttpClient.send(storageHost, "records/" + recordId, "DELETE",
+          headers, "", "");
+      assertEquals(204, storageResponse.getStatus());
+    }
+  }
+
+}
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/TestUtils.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/TestUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..ee4f21322ae7edff314b1e83a7d12af203f7be44
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/TestUtils.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2017-2020, Schlumberger
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.net.URL;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.*;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public abstract class TestUtils {
+
+    protected String serviceAccountFile;
+    protected static String opsToken = null;
+    protected static String adminToken = null;
+    protected static String editorToken = null;
+    protected static String noAccessToken = null;
+
+    public static String getApiPath(String api, boolean enforceHttp) throws Exception {
+        String baseUrl = Config.Instance().HostUrl;
+        if (enforceHttp)
+            baseUrl = baseUrl.replaceFirst("https", "http");
+        URL mergedURL = new URL(baseUrl + api);
+        return mergedURL.toString();
+    }
+
+    public static String getApiPath(String baseUrl, String api, boolean enforceHttp) throws Exception {
+        if (enforceHttp)
+            baseUrl = baseUrl.replaceFirst("https", "http");
+        URL mergedURL = new URL(baseUrl + api);
+        return mergedURL.toString();
+    }
+
+    public static String getOsduTenant() {
+        return Config.Instance().OsduTenant;
+    }
+
+    public static String getCustomerTenant() {
+        return Config.Instance().ClientTenant;
+    }
+
+    public abstract String getOpsToken() throws Exception;
+
+    public abstract String getAdminToken() throws Exception;
+
+    public abstract String getEditorToken() throws Exception;
+
+    public abstract String getNoAccessToken() throws Exception;
+
+    public static ClientResponse send(String path, String httpMethod, String token, String requestBody, String query,
+                                      Map<String, String> headers, boolean enforceHttp)
+            throws Exception {
+        ClientResponse response = null;
+        Client client = getClient();
+        client.setConnectTimeout(300000);
+        client.setReadTimeout(300000);
+        client.setFollowRedirects(false);
+        String url = getApiPath(path + query, enforceHttp);
+        WebResource webResource = client.resource(url);
+        int count = 1;
+        int MaxRetry = 3;
+        while (count < MaxRetry) {
+            try {
+                headers.put("correlation-id", headers.getOrDefault("correlation-id", UUID.randomUUID().toString()));
+                WebResource.Builder builder = webResource.type(MediaType.APPLICATION_JSON)
+                        .header("Authorization", token);
+                headers.forEach((k, v) -> builder.header(k, v));
+                //removing Auth header before logging
+                headers.remove("Authorization");
+                log.info(String.format("\nRequest URL: %s %s\nRequest Headers: %s\nRequest Body: %s", httpMethod, url, headers, indentatedBody(requestBody)));
+                log.info(String.format("Attempt: #%s/%s, CorrelationId: %s", count, MaxRetry, headers.get("correlation-id")));
+                response = builder.method(httpMethod, ClientResponse.class, requestBody);
+                if (response.getStatusInfo().getFamily().equals(Response.Status.Family.valueOf("SERVER_ERROR"))) {
+                    count++;
+                    Thread.sleep(5000);
+                    continue;
+                } else {
+                    break;
+                }
+            } catch (Exception ex) {
+                log.error("Exception While Making Request: ", ex);
+                count++;
+                if (count == MaxRetry) {
+                    throw new AssertionError("Error: Send request error", ex);
+                }
+            } finally {
+                //log response body
+                log.info("sending response from TestUtils send method");
+                if(response!=null)
+                    log.info(String.format("\nThis is the response received : %s\nResponse Headers: %s\nResponse Status code: %s", response, response.getHeaders(), response.getStatus()));
+            }
+        }
+        return response;
+    }
+
+    public static ClientResponse send(String url, String path, String httpMethod, String token, String requestBody,
+                                      String query, Map<String, String> headers, boolean enforceHttp)
+            throws Exception {
+        ClientResponse response = null;
+        Client client = getClient();
+        client.setConnectTimeout(300000);
+        client.setReadTimeout(300000);
+        client.setFollowRedirects(false);
+        String URL = getApiPath(url, path + query, enforceHttp);
+        WebResource webResource = client.resource(URL);
+        int count = 1;
+        int MaxRetry = 3;
+        while (count < MaxRetry) {
+            try {
+                headers.put("correlation-id", headers.getOrDefault("correlation-id", UUID.randomUUID().toString()));
+                WebResource.Builder builder = webResource.type(MediaType.APPLICATION_JSON);
+                if (!token.isEmpty()) {
+                    log.info("Token is not empty so adding to request header");
+                    builder.header("Authorization", token);
+                }
+                headers.forEach((k, v) -> builder.header(k, v));
+                //removing Auth header before logging
+                headers.remove("Authorization");
+                log.info(String.format("\nRequest URL: %s %s\nRequest Headers: %s\nRequest Body: %s", httpMethod, URL, headers, indentatedBody(requestBody)));
+                log.info(String.format("Attempt: #%s/%s, CorrelationId: %s", count, MaxRetry, headers.get("correlation-id")));
+                response = builder.method(httpMethod, ClientResponse.class, requestBody);
+                if (response.getStatusInfo().getFamily().equals(Response.Status.Family.valueOf("SERVER_ERROR"))) {
+                    count++;
+                    Thread.sleep(5000);
+                    continue;
+                } else {
+                    break;
+                }
+            } catch (Exception ex) {
+                log.error("Exception While Making Request: ", ex);
+                count++;
+                if (count == MaxRetry) {
+                    throw new AssertionError("Error: Send request error", ex);
+                }
+            } finally {
+                //log response body
+                log.info("sending response from TestUtils send method");
+                if(response!=null)
+                    log.info(String.format("\nThis is the response received : %s\nResponse Headers: %s\nResponse Status code: %s", response, response.getHeaders(), response.getStatus()));
+            }
+        }
+        return response;
+    }
+
+    public static String indentatedBody(String responseBody) {
+        JsonParser jsonParser = new JsonParser();
+        if( responseBody== null)
+            return responseBody;
+        JsonElement jsonElement = jsonParser.parse(responseBody);
+        String indentedResponseEntity =new GsonBuilder().setPrettyPrinting().create().toJson(jsonElement);
+        return indentedResponseEntity;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> T getResult(ClientResponse response, int exepectedStatus, Class<T> classOfT) {
+        String json = response.getEntity(String.class);
+
+        assertEquals(exepectedStatus, response.getStatus());
+        if (exepectedStatus == 204) {
+            return null;
+        }
+
+        assertEquals(MediaType.APPLICATION_JSON, response.getType().toString());
+        if (classOfT == String.class) {
+            return (T) json;
+        }
+
+        Gson gson = new Gson();
+        return gson.fromJson(json, classOfT);
+    }
+
+    public static Client getClient() {
+        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
+            @Override
+            public X509Certificate[] getAcceptedIssuers() {
+                return null;
+            }
+
+            @Override
+            public void checkClientTrusted(X509Certificate[] certs, String authType) {
+            }
+
+            @Override
+            public void checkServerTrusted(X509Certificate[] certs, String authType) {
+            }
+        }};
+
+        try {
+            SSLContext sc = SSLContext.getInstance("TLS");
+            sc.init(null, trustAllCerts, new SecureRandom());
+            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+        } catch (Exception e) {
+            log.error("Exception occurred", e);
+        }
+        log.info("Creating client");
+        return Client.create();
+    }
+}
\ No newline at end of file
diff --git a/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/TokenTestUtils.java b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/TokenTestUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..76c0845035573e167123ba019ffed28b29cf4227
--- /dev/null
+++ b/notification-acceptance-test/src/test/java/org/opengroup/osdu/notification/util/TokenTestUtils.java
@@ -0,0 +1,66 @@
+/*
+  Copyright 2002-2022 Google LLC
+  Copyright 2002-2022 EPAM Systems, Inc
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+package org.opengroup.osdu.notification.util;
+
+import com.google.common.base.Strings;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class TokenTestUtils extends TestUtils {
+
+  public static final String DE_ADMIN_USER_TOKEN = "DE_ADMIN_USER_TOKEN";
+  public static final String DE_EDITOR_USER_TOKEN = "DE_EDITOR_USER_TOKEN";
+  public static final String DE_OPS_USER_TOKEN = "DE_OPS_USER_TOKEN";
+  public static final String NO_ACCESS_USER_TOKEN = "NO_ACCESS_USER_TOKEN";
+  private OpenIDTokenProvider tokenProvider;
+
+  public TokenTestUtils() {
+    adminToken = System.getProperty(DE_ADMIN_USER_TOKEN, System.getenv(DE_ADMIN_USER_TOKEN));
+    editorToken = System.getProperty(DE_EDITOR_USER_TOKEN, System.getenv(DE_EDITOR_USER_TOKEN));
+    opsToken = System.getProperty(DE_OPS_USER_TOKEN, System.getenv(DE_OPS_USER_TOKEN));
+    noAccessToken = System.getProperty(NO_ACCESS_USER_TOKEN, System.getenv(NO_ACCESS_USER_TOKEN));
+
+    if (Strings.isNullOrEmpty(adminToken) || Strings.isNullOrEmpty(editorToken) || Strings.isNullOrEmpty(opsToken) || Strings.isNullOrEmpty(noAccessToken)) {
+      tokenProvider = new OpenIDTokenProvider();
+      adminToken = getAdminToken();
+      editorToken = getEditorToken();
+      opsToken = getOpsToken();
+      noAccessToken = getNoAccessToken();
+    }
+  }
+
+  @Override
+  public synchronized String getOpsToken() {
+    return "Bearer " + tokenProvider.getOpsAccessToken();
+  }
+
+  @Override
+  public synchronized String getAdminToken() {
+    return "Bearer " + tokenProvider.getAdminAccessToken();
+  }
+
+  @Override
+  public synchronized String getEditorToken() {
+    return "Bearer " + tokenProvider.getEditorAccessToken();
+  }
+
+  @Override
+  public synchronized String getNoAccessToken() {
+    return "Bearer " + tokenProvider.getNoAccessToken();
+  }
+}
\ No newline at end of file
diff --git a/notification-acceptance-test/src/test/resources/LegalTag.json b/notification-acceptance-test/src/test/resources/LegalTag.json
new file mode 100644
index 0000000000000000000000000000000000000000..e9629c72a833ef80a685f15d028679e75ac6edb6
--- /dev/null
+++ b/notification-acceptance-test/src/test/resources/LegalTag.json
@@ -0,0 +1,16 @@
+{
+  "name": "{{tagName}}",
+  "properties": {
+    "countryOfOrigin": [
+      "US"
+    ],
+    "contractId": "A1234",
+    "expirationDate": 2222222222222,
+    "originator": "Default",
+    "dataType": "Public Domain Data",
+    "securityClassification": "Public",
+    "personalData": "No Personal Data",
+    "exportClassification": "EAR99"
+  },
+  "description": "Test legal tag for notification"
+}
\ No newline at end of file
diff --git a/notification-acceptance-test/src/test/resources/StorageRecord.json b/notification-acceptance-test/src/test/resources/StorageRecord.json
new file mode 100644
index 0000000000000000000000000000000000000000..c4a8e395f201baf6ce31ca39d0de01968a2439a3
--- /dev/null
+++ b/notification-acceptance-test/src/test/resources/StorageRecord.json
@@ -0,0 +1,33 @@
+[
+  {
+    "id": "{{data-partition-id}}:dataset--ConnectedSource.Generic:notification-test-{{ids-suffix}}",
+    "kind": "{{data-partition-id}}:wks:dataset--ConnectedSource.Generic:1.0.0",
+    "data": {
+      "Name": "name",
+      "DatasetProperties": {
+        "ConnectedSourceDataJobId": "no-data",
+        "ConnectedSourceRegistryEntryId": "no-data",
+        "SourceDataPartitionId": "no-data",
+        "SourceRecordId": "no-data"
+      }
+    },
+    "namespace": "{{data-partition-id}}:osdu",
+    "legal": {
+      "legaltags": [
+        "{{legal-tag}}"
+      ],
+      "otherRelevantDataCountries": [
+        "US"
+      ],
+      "status": "compliant"
+    },
+    "acl": {
+      "viewers": [
+        "data.default.viewers@{{data-partition-id}}.{{group_id}}"
+      ],
+      "owners": [
+        "data.default.owners@{{data-partition-id}}.{{group_id}}"
+      ]
+    }
+  }
+]
\ No newline at end of file
diff --git a/notification-acceptance-test/src/test/resources/logback-test.xml b/notification-acceptance-test/src/test/resources/logback-test.xml
new file mode 100644
index 0000000000000000000000000000000000000000..22c6175d45f368b69c4006deb9ebf3ff5084579a
--- /dev/null
+++ b/notification-acceptance-test/src/test/resources/logback-test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+  <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
+  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%yellow([%thread]) %highlight(| %-5level |) %green(%d) %cyan(| %logger{15} |) %highlight(%msg) %n</pattern>
+      <charset>utf8</charset>
+    </encoder>
+  </appender>
+  <root level="INFO">
+    <appender-ref ref="CONSOLE" />
+  </root>
+</configuration>