diff --git a/.github/workflows/at_server.yaml b/.github/workflows/at_server.yaml
index 539d3b454..8b57de360 100644
--- a/.github/workflows/at_server.yaml
+++ b/.github/workflows/at_server.yaml
@@ -171,7 +171,7 @@ jobs:
           ls -laR tools/build_virtual_environment/ve/*
 
       - name: Build docker image
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           file: tools/build_virtual_environment/ve/Dockerfile
           context: tools/build_virtual_environment/ve
@@ -209,7 +209,7 @@ jobs:
       # On push event, upload secondary server binary
       - name: upload secondary server
         if: ${{ github.event_name == 'push' }}
-        uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
+        uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
         with:
           name: secondary-server
           path: packages/at_secondary_server/secondary
@@ -238,10 +238,10 @@ jobs:
           grep version pubspec.yaml | head -1
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -249,7 +249,7 @@ jobs:
       # Builds and pushes the secondary server image to docker hub.
       - name: Build and push secondary image for x64
         id: docker_build_secondary
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           push: true
           provenance: false
@@ -594,13 +594,13 @@ jobs:
         run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
+        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -608,7 +608,7 @@ jobs:
       # Builds and pushes the at_virtual_env to docker hub.
       - name: Build and push
         id: docker_build
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           file: tools/build_virtual_environment/ve/Dockerfile.vip
           context: .
@@ -648,13 +648,13 @@ jobs:
         run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
+        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -662,7 +662,7 @@ jobs:
       # Builds and pushes the secondary server image to docker hub.
       - name: Build and push secondary image for amd64 and arm64
         id: docker_build_secondary
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           push: true
           provenance: false
@@ -703,13 +703,13 @@ jobs:
         run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
+        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -717,7 +717,7 @@ jobs:
       # Builds and pushes the secondary server image to docker hub.
       - name: Build and push secondary image for amd64 and arm64
         id: docker_build_observable_secondary
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           push: true
           provenance: false
@@ -756,13 +756,13 @@ jobs:
           grep version pubspec.yaml | head -1
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
+        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -770,7 +770,7 @@ jobs:
       # Builds and pushes the secondary server image to docker hub.
       - name: Build and push secondary image for amd64 and arm64
         id: docker_build_secondary
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           push: true
           provenance: false
@@ -801,20 +801,20 @@ jobs:
           grep version pubspec.yaml | head -1
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
+        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
 
       - name: Build and push
         id: docker_build
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           file: tools/build_virtual_environment/ve/Dockerfile.vip
           context: .
@@ -847,13 +847,13 @@ jobs:
         run: echo "VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
+        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -861,7 +861,7 @@ jobs:
       # Builds and pushes the secondary server image to docker hub.
       - name: Build and push secondary image for amd64 and arm64
         id: docker_build_secondary
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           push: true
           provenance: false
@@ -882,20 +882,20 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
+        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
 
       - name: Build and push
         id: docker_build
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           file: tools/build_virtual_environment/ve/Dockerfile.vip
           context: .
diff --git a/.github/workflows/at_server_dev_deploy.yaml b/.github/workflows/at_server_dev_deploy.yaml
index ebe6b18bb..75664ab02 100644
--- a/.github/workflows/at_server_dev_deploy.yaml
+++ b/.github/workflows/at_server_dev_deploy.yaml
@@ -21,17 +21,17 @@ jobs:
         run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
 
       # Build the Docker image for Dev
       - name: Build and push
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           file: packages/at_root_server/Dockerfile
           context: packages/at_root_server
diff --git a/.github/workflows/at_server_prod_deploy.yaml b/.github/workflows/at_server_prod_deploy.yaml
index 4633a5e3b..83ec6ad26 100644
--- a/.github/workflows/at_server_prod_deploy.yaml
+++ b/.github/workflows/at_server_prod_deploy.yaml
@@ -21,17 +21,17 @@ jobs:
         run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
 
       # Build the Docker image for Dev
       - name: Build and push
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           file: packages/at_root_server/Dockerfile
           context: packages/at_root_server
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index d06526571..ee8c1c249 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -24,4 +24,4 @@ jobs:
       - name: 'Checkout Repository'
         uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
       - name: 'Dependency Review'
-        uses: actions/dependency-review-action@f6fff72a3217f580d5afd49a46826795305b63c7 # v3.0.8
+        uses: actions/dependency-review-action@6c5ccdad469c9f8a2996bfecaec55a631a347034 # v3.1.0
diff --git a/.github/workflows/promote_canary.yaml b/.github/workflows/promote_canary.yaml
index 0c1e87c9d..46d74ec1c 100644
--- a/.github/workflows/promote_canary.yaml
+++ b/.github/workflows/promote_canary.yaml
@@ -22,13 +22,13 @@ jobs:
         run: echo "VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
+        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -36,7 +36,7 @@ jobs:
       # Builds and pushes the secondary server image to docker hub.
       - name: Build and push secondary image for amd64 and arm64
         id: docker_build_canary_to_prod
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           push: true
           provenance: false
@@ -63,13 +63,13 @@ jobs:
         run: echo "VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
+        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -77,7 +77,7 @@ jobs:
       # Builds and pushes the secondary server image to docker hub.
       - name: Build and push virtualenv image for amd64 and arm64
         id: docker_build_canary_to_vip
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           push: true
           file: tools/build_virtual_environment/ve/Dockerfile.canary_to_vip
diff --git a/.github/workflows/revert_secondary.yaml b/.github/workflows/revert_secondary.yaml
index 0b4e2d088..b08d90f0d 100644
--- a/.github/workflows/revert_secondary.yaml
+++ b/.github/workflows/revert_secondary.yaml
@@ -29,7 +29,7 @@ jobs:
         run: echo "VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -72,7 +72,7 @@ jobs:
         run: echo "VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 1b859e7a7..b986c0e9a 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -59,7 +59,7 @@ jobs:
       # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
       # format to the repository Actions tab.
       - name: "Upload artifact"
-        uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
+        uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
         with:
           name: SARIF file
           path: results.sarif
diff --git a/.github/workflows/ve_base.yaml b/.github/workflows/ve_base.yaml
index 284ee0184..950be157f 100644
--- a/.github/workflows/ve_base.yaml
+++ b/.github/workflows/ve_base.yaml
@@ -12,20 +12,20 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
+        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
+        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
 
       - name: Login to DockerHub
-        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
 
       - name: Build and push
         id: docker_build
-        uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1
+        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
         with:
           file: tools/build_virtual_environment/ve_base/Dockerfile
           push: true
diff --git a/packages/at_root_server/Dockerfile b/packages/at_root_server/Dockerfile
index 4ccc08daa..eaf0996e7 100644
--- a/packages/at_root_server/Dockerfile
+++ b/packages/at_root_server/Dockerfile
@@ -1,4 +1,4 @@
-FROM dart:3.1.0@sha256:96d2e5d03b8356c2a7542716ace7dce745971efe1d03888a1d7ecd2e7c1dde36 AS buildimage
+FROM dart:3.1.1@sha256:ec7bb9e577648ea5526c9daf714e9bc7af670ce7c93b594205e68c14a10cea3b AS buildimage
 ENV HOMEDIR=/atsign
 ENV BINARYDIR=/usr/local/at
 ENV USER_ID=1024
diff --git a/packages/at_secondary_server/CHANGELOG.md b/packages/at_secondary_server/CHANGELOG.md
index 3eafd44bf..ff53a0fa9 100644
--- a/packages/at_secondary_server/CHANGELOG.md
+++ b/packages/at_secondary_server/CHANGELOG.md
@@ -1,3 +1,5 @@
+## 3.0.36
+- fix: Implement notify ephemeral changes - Send notification with value without caching the key on receiver's secondary server 
 ## 3.0.35
 - chore: Upgraded at_persistence_secondary_server to 3.0.57 for memory optimization in commit log
 - feat: APKAM keys verb implementation
diff --git a/packages/at_secondary_server/config/config.yaml b/packages/at_secondary_server/config/config.yaml
index fabcabd2e..4b27f99e4 100644
--- a/packages/at_secondary_server/config/config.yaml
+++ b/packages/at_secondary_server/config/config.yaml
@@ -149,3 +149,13 @@ sync:
 #IMPORTANT NOTE : please set testingMode to true only if you know what you're doing. Set to false when not testing
 testing:
   testingMode: false
+
+# APKAM enrollment configurations
+enrollment:
+  # The maximum time in hours for an enrollment to expire, beyond which any action on enrollment is forbidden.
+  # Default values is 48 hours.
+  expiryInHours: 48
+  # The maximum number of requests allowed within the time window.
+  maxRequestsPerTimeFrame: 5
+  # The duration of the time window in hours.
+  timeFrameInHours: 1
\ No newline at end of file
diff --git a/packages/at_secondary_server/lib/src/connection/inbound/dummy_inbound_connection.dart b/packages/at_secondary_server/lib/src/connection/inbound/dummy_inbound_connection.dart
index 8b06dc40a..f7b6f748b 100644
--- a/packages/at_secondary_server/lib/src/connection/inbound/dummy_inbound_connection.dart
+++ b/packages/at_secondary_server/lib/src/connection/inbound/dummy_inbound_connection.dart
@@ -1,12 +1,19 @@
 import 'dart:io';
 
 import 'package:at_secondary/src/connection/inbound/inbound_connection_metadata.dart';
+import 'package:at_secondary/src/server/at_secondary_config.dart';
 import 'package:at_server_spec/at_server_spec.dart';
 
 /// A dummy implementation of [InboundConnection] class which returns a dummy inbound connection.
 class DummyInboundConnection implements InboundConnection {
   var metadata = InboundConnectionMetadata();
 
+  @override
+  int maxRequestsPerTimeFrame = AtSecondaryConfig.maxEnrollRequestsAllowed;
+  
+  @override
+  int timeFrameInMillis = AtSecondaryConfig.timeFrameInMills;
+
   @override
   void acceptRequests(Function(String p1, InboundConnection p2) callback,
       Function(List<int>, InboundConnection) streamCallback) {}
@@ -54,4 +61,9 @@ class DummyInboundConnection implements InboundConnection {
 
   @override
   Socket? receiverSocket;
+
+  @override
+  bool isRequestAllowed() {
+    return true;
+  }
 }
diff --git a/packages/at_secondary_server/lib/src/connection/inbound/inbound_connection_impl.dart b/packages/at_secondary_server/lib/src/connection/inbound/inbound_connection_impl.dart
index a19bf6b82..dafbae817 100644
--- a/packages/at_secondary_server/lib/src/connection/inbound/inbound_connection_impl.dart
+++ b/packages/at_secondary_server/lib/src/connection/inbound/inbound_connection_impl.dart
@@ -1,3 +1,4 @@
+import 'dart:collection';
 import 'dart:io';
 import 'dart:math';
 
@@ -5,6 +6,7 @@ import 'package:at_secondary/src/connection/base_connection.dart';
 import 'package:at_secondary/src/connection/inbound/inbound_connection_metadata.dart';
 import 'package:at_secondary/src/connection/inbound/inbound_connection_pool.dart';
 import 'package:at_secondary/src/connection/inbound/inbound_message_listener.dart';
+import 'package:at_secondary/src/server/at_secondary_config.dart';
 import 'package:at_secondary/src/server/server_context.dart';
 import 'package:at_secondary/src/server/at_secondary_impl.dart';
 import 'package:at_secondary/src/utils/logging_util.dart';
@@ -42,6 +44,17 @@ class InboundConnectionImpl extends BaseConnection
   late double lowWaterMarkRatio;
   late bool progressivelyReduceAllowableInboundIdleTime;
 
+  /// The maximum number of requests allowed within the specified time frame.
+  @override
+  late int maxRequestsPerTimeFrame;
+
+  /// The duration of the time frame within which requests are limited.
+  @override
+  late int timeFrameInMillis;
+
+  /// A list of timestamps representing the times when requests were made.
+  late final Queue<int> requestTimestampQueue;
+
   InboundConnectionImpl(Socket? socket, String? sessionId, {this.owningPool})
       : super(socket) {
     metaData = InboundConnectionMetadata()
@@ -69,6 +82,10 @@ class InboundConnectionImpl extends BaseConnection
         secondaryContext.authenticatedInboundIdleTimeMillis;
     authenticatedMinAllowableIdleTimeMillis =
         secondaryContext.authenticatedMinAllowableIdleTimeMillis;
+
+    maxRequestsPerTimeFrame = AtSecondaryConfig.maxEnrollRequestsAllowed;
+    timeFrameInMillis = AtSecondaryConfig.timeFrameInMills;
+    requestTimestampQueue = Queue();
   }
 
   /// Returns true if the underlying socket is not null and socket's remote address and port match.
@@ -230,4 +247,31 @@ class InboundConnectionImpl extends BaseConnection
           metaData, 'SENT: ${BaseConnection.truncateForLogging(data)}'));
     }
   }
+
+  @override
+  bool isRequestAllowed() {
+    int currentTimeInMills = DateTime.now().toUtc().millisecondsSinceEpoch;
+    _checkAndUpdateQueue(currentTimeInMills);
+    if (requestTimestampQueue.length < maxRequestsPerTimeFrame) {
+      requestTimestampQueue.addLast(currentTimeInMills);
+      return true;
+    }
+    return false;
+  }
+
+  /// Checks and updates the request timestamp queue based on the current time.
+  ///
+  /// This method removes timestamps from the queue that are older than the specified
+  /// time window.
+  ///
+  /// [currentTimeInMillis] is the current time in milliseconds since epoch.
+  void _checkAndUpdateQueue(int currentTimeInMillis) {
+    if (requestTimestampQueue.isEmpty) return;
+    int calculatedTime = (currentTimeInMillis - requestTimestampQueue.first);
+    while (calculatedTime >= timeFrameInMillis) {
+      requestTimestampQueue.removeFirst();
+      if (requestTimestampQueue.isEmpty) break;
+      calculatedTime = (currentTimeInMillis - requestTimestampQueue.first);
+    }
+  }
 }
diff --git a/packages/at_secondary_server/lib/src/exception/global_exception_handler.dart b/packages/at_secondary_server/lib/src/exception/global_exception_handler.dart
index ecfe7ec17..c3faead04 100644
--- a/packages/at_secondary_server/lib/src/exception/global_exception_handler.dart
+++ b/packages/at_secondary_server/lib/src/exception/global_exception_handler.dart
@@ -68,7 +68,8 @@ class GlobalExceptionHandler {
         exception is KeyNotFoundException ||
         exception is AtConnectException ||
         exception is SocketException ||
-        exception is AtTimeoutException) {
+        exception is AtTimeoutException ||
+        exception is AtThrottleLimitExceeded) {
       logger.info(exception.toString());
       await _sendResponseForException(exception, atConnection);
     } else if (exception is InternalServerError) {
diff --git a/packages/at_secondary_server/lib/src/notification/resource_manager.dart b/packages/at_secondary_server/lib/src/notification/resource_manager.dart
index 245a7ab2d..277852da3 100644
--- a/packages/at_secondary_server/lib/src/notification/resource_manager.dart
+++ b/packages/at_secondary_server/lib/src/notification/resource_manager.dart
@@ -35,6 +35,7 @@ class ResourceManager {
       NotifyConnectionsPool.getInstance();
 
   int get outboundConnectionLimit => _notifyConnectionsPool.size;
+
   set outboundConnectionLimit(int ocl) => _notifyConnectionsPool.size = ocl;
 
   void start() {
@@ -201,6 +202,9 @@ class ResourceManager {
     // looked at this code.
     String commandBody;
     commandBody = '${atNotification.notification}';
+    if (atNotification.atValue != null) {
+      commandBody = '$commandBody:${atNotification.atValue}';
+    }
     var atMetaData = atNotification.atMetadata;
     if (atMetaData != null) {
       if (atNotification.atMetadata!.skeEncAlgo != null) {
@@ -237,7 +241,7 @@ class ResourceManager {
       }
       if (atMetaData.ttr != null) {
         commandBody =
-            'ttr:${atMetaData.ttr}:ccd:${atMetaData.isCascade}:$commandBody:${atNotification.atValue}';
+            'ttr:${atMetaData.ttr}:ccd:${atMetaData.isCascade}:$commandBody';
       }
       if (atMetaData.ttb != null) {
         commandBody = 'ttb:${atMetaData.ttb}:$commandBody';
diff --git a/packages/at_secondary_server/lib/src/server/at_secondary_config.dart b/packages/at_secondary_server/lib/src/server/at_secondary_config.dart
index 65a4f9ec6..e3844660a 100644
--- a/packages/at_secondary_server/lib/src/server/at_secondary_config.dart
+++ b/packages/at_secondary_server/lib/src/server/at_secondary_config.dart
@@ -44,17 +44,22 @@ class AtSecondaryConfig {
 
   //Notification
   static const bool _autoNotify = true;
+
   // The maximum number of retries for a notification.
   static const int _maxNotificationRetries = 30;
+
   // The quarantine duration of an atsign. Notifications will be retried max_retries times, every quarantineDuration seconds approximately.
   static const int _notificationQuarantineDuration = 10;
+
   // The notifications queue will be processed every jobFrequency seconds. However, the notifications queue will always be processed
   // *immediately* when a new notification is queued. When that happens, the queue processing will not run again until jobFrequency
   // seconds have passed since the last queue-processing run completed.
   static const int _notificationJobFrequency = 11;
+
   // The time interval(in seconds) to notify latest commitID to monitor connections
   // To disable to the feature, set to -1.
   static const int _statsNotificationJobTimeInterval = 15;
+
   // defines the time after which a notification expires in units of minutes. Notifications expire after 1440 minutes or 24 hours by default.
   static const int _notificationExpiresAfterMins = 1440;
 
@@ -120,6 +125,20 @@ class AtSecondaryConfig {
 
   static String? get secondaryServerVersion => _secondaryServerVersion;
 
+  // Enrollment Configurations
+  static const int _enrollmentExpiryInHours = 48;
+  static int _maxEnrollRequestsAllowed = 5;
+
+  static final int _timeFrameInHours = 1;
+
+  // For easy of testing, duration in hours is long. Hence introduced "timeFrameInMills"
+  // to have a shorter time frame. This is defaulted to "_timeFrameInHours", can be modified
+  // via the config verb
+  static int _timeFrameInMills =
+      Duration(hours: _timeFrameInHours).inMilliseconds;
+
+  static int get enrollmentExpiryInHours => _enrollmentExpiryInHours;
+
   // TODO: Medium priority: Most (all?) getters in this class return a default value but the signatures currently
   //  allow for nulls. Should fix this as has been done for logLevel
   // TODO: Low priority: Lots of very similar boilerplate code here. Not necessarily bad in this particular case, but
@@ -707,6 +726,54 @@ class AtSecondaryConfig {
     }
   }
 
+  static int get maxEnrollRequestsAllowed {
+    // For easy of testing purpose, we need to reduce the number of requests.
+    // So, in testing mode, enable to modify the "maxEnrollRequestsAllowed"
+    // can be set via the config verb
+    // Defaults to value in config.yaml
+    if (testingMode) {
+      return _maxEnrollRequestsAllowed;
+    }
+    var result = _getIntEnvVar('maxEnrollRequestsAllowed');
+    if (result != null) {
+      return result;
+    }
+    try {
+      return getConfigFromYaml(['enrollment', 'maxRequestsPerTimeFrame']);
+    } on ElementNotFoundException {
+      return _maxEnrollRequestsAllowed;
+    }
+  }
+
+  static set maxEnrollRequestsAllowed(int value) {
+    _maxEnrollRequestsAllowed = value;
+  }
+
+  static int get timeFrameInMills {
+    // For easy of testing purpose, we need to reduce the time frame.
+    // So, in testing mode, enable to modify the "timeFrameInMills"
+    // can be set via the config verb
+    // Defaults to value in config.yaml
+    if (testingMode) {
+      return _timeFrameInMills;
+    }
+    var result = _getIntEnvVar('enrollTimeFrameInHours');
+    if (result != null) {
+      return Duration(hours: result).inMilliseconds;
+    }
+    try {
+      return Duration(
+              hours: getConfigFromYaml(['enrollment', 'timeFrameInHours']))
+          .inMilliseconds;
+    } on ElementNotFoundException {
+      return Duration(hours: _timeFrameInHours).inMilliseconds;
+    }
+  }
+
+  static set timeFrameInMills(int timeWindowInMills) {
+    _timeFrameInMills = timeWindowInMills;
+  }
+
   //implementation for config:set. This method returns a data stream which subscribers listen to for updates
   static Stream<dynamic>? subscribe(ModifiableConfigs configName) {
     if (testingMode) {
@@ -777,6 +844,10 @@ class AtSecondaryConfig {
         return false;
       case ModifiableConfigs.doCacheRefreshNow:
         return false;
+      case ModifiableConfigs.maxRequestsPerTimeFrame:
+        return maxEnrollRequestsAllowed;
+      case ModifiableConfigs.timeFrameInMills:
+        return Duration(hours: _timeFrameInHours).inMilliseconds;
     }
   }
 
@@ -857,7 +928,9 @@ enum ModifiableConfigs {
   maxNotificationRetries,
   checkCertificateReload,
   shouldReloadCertificates,
-  doCacheRefreshNow
+  doCacheRefreshNow,
+  maxRequestsPerTimeFrame,
+  timeFrameInMills
 }
 
 class ModifiableConfigurationEntry {
diff --git a/packages/at_secondary_server/lib/src/server/at_secondary_impl.dart b/packages/at_secondary_server/lib/src/server/at_secondary_impl.dart
index 932d65a54..bc20ae6e3 100644
--- a/packages/at_secondary_server/lib/src/server/at_secondary_impl.dart
+++ b/packages/at_secondary_server/lib/src/server/at_secondary_impl.dart
@@ -421,6 +421,14 @@ class AtSecondaryServerImpl implements AtSecondaryServer {
         notificationResourceManager.setMaxRetries(newCount);
         QueueManager.getInstance().setMaxRetries(newCount);
       });
+
+      AtSecondaryConfig.subscribe(ModifiableConfigs.maxRequestsPerTimeFrame)?.listen((maxEnrollRequestsAllowed) {
+        AtSecondaryConfig.maxEnrollRequestsAllowed = maxEnrollRequestsAllowed;
+      });
+
+      AtSecondaryConfig.subscribe(ModifiableConfigs.timeFrameInMills)?.listen((timeWindowInMills) {
+        AtSecondaryConfig.timeFrameInMills = timeWindowInMills;
+      });
     }
   }
 
diff --git a/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart
index 85f87a2ee..5e0c52b50 100644
--- a/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart
+++ b/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart
@@ -11,6 +11,7 @@ import 'package:at_secondary/src/verb/manager/response_handler_manager.dart';
 import 'package:at_server_spec/at_server_spec.dart';
 import 'package:at_server_spec/at_verb_spec.dart';
 import 'package:at_utils/at_logger.dart';
+import 'package:at_secondary/src/utils/secondary_util.dart';
 
 final String paramFullCommandAsReceived = 'FullCommandAsReceived';
 
@@ -89,6 +90,10 @@ abstract class AbstractVerbHandler implements VerbHandler {
       AtData enrollData = await keyStore.get(enrollmentKey);
       EnrollDataStoreValue enrollDataStoreValue =
           EnrollDataStoreValue.fromJson(jsonDecode(enrollData.data!));
+      if (!SecondaryUtil.isActiveKey(enrollData) &&
+          enrollDataStoreValue.approval!.state != EnrollStatus.approved.name) {
+        enrollDataStoreValue.approval?.state = EnrollStatus.expired.name;
+      }
       return enrollDataStoreValue;
     } on KeyNotFoundException {
       logger.severe('$enrollmentKey does not exist in the keystore');
diff --git a/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart
index f18a017fb..31b43f930 100644
--- a/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart
+++ b/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart
@@ -4,12 +4,14 @@ import 'dart:convert';
 import 'package:at_commons/at_commons.dart';
 import 'package:at_secondary/src/connection/inbound/inbound_connection_metadata.dart';
 import 'package:at_secondary/src/server/at_secondary_impl.dart';
+import 'package:at_secondary/src/server/at_secondary_config.dart';
 import 'package:at_secondary/src/constants/enroll_constants.dart';
 import 'package:at_secondary/src/enroll/enroll_datastore_value.dart';
 import 'package:at_secondary/src/utils/notification_util.dart';
 import 'package:at_secondary/src/verb/handler/otp_verb_handler.dart';
 import 'package:at_server_spec/at_server_spec.dart';
 import 'package:at_server_spec/at_verb_spec.dart';
+import 'package:meta/meta.dart';
 import 'package:uuid/uuid.dart';
 import 'abstract_verb_handler.dart';
 import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart';
@@ -26,6 +28,10 @@ class EnrollVerbHandler extends AbstractVerbHandler {
   @override
   Verb getVerb() => enrollVerb;
 
+  @visibleForTesting
+  int enrollmentExpiryInMills =
+      Duration(hours: AtSecondaryConfig.enrollmentExpiryInHours).inMilliseconds;
+
   @override
   Future<void> processVerb(
       Response response,
@@ -40,23 +46,31 @@ class EnrollVerbHandler extends AbstractVerbHandler {
       throw UnAuthenticatedException(
           'Cannot $operation enrollment without authentication');
     }
+    EnrollParams? enrollVerbParams;
     try {
-      var enrollVerbParams;
-      if (verbParams[enrollParams] != null) {
+      // Ensure that enrollParams are present for all enroll operation
+      // Exclude operation 'list' which does not have enrollParams
+      if (verbParams[enrollParams] == null) {
+        if (operation != 'list') {
+          logger.severe(
+              'Enroll params is empty | EnrollParams: ${verbParams[enrollParams]}');
+          throw IllegalArgumentException('Enroll parameters not provided');
+        }
+      } else {
         enrollVerbParams =
             EnrollParams.fromJson(jsonDecode(verbParams[enrollParams]!));
       }
       switch (operation) {
         case 'request':
           await _handleEnrollmentRequest(
-              enrollVerbParams, currentAtSign, responseJson, atConnection);
+              enrollVerbParams!, currentAtSign, responseJson, atConnection);
           break;
 
         case 'approve':
         case 'deny':
         case 'revoke':
-          await _handleEnrollmentPermissions(
-              enrollVerbParams, currentAtSign, operation, responseJson);
+          await _handleEnrollmentPermissions(enrollVerbParams!, currentAtSign,
+              operation, responseJson, response);
           break;
 
         case 'list':
@@ -65,14 +79,11 @@ class EnrollVerbHandler extends AbstractVerbHandler {
           return;
       }
     } catch (e, stackTrace) {
-      response.isError = true;
-      response.errorMessage = e.toString();
-      responseJson['status'] = 'exception';
-      responseJson['reason'] = e.toString();
       logger.severe('Exception: $e\n$stackTrace');
       rethrow;
     }
     response.data = jsonEncode(responseJson);
+    return;
   }
 
   /// Enrollment requests details are persisted in the keystore and are excluded from
@@ -94,11 +105,17 @@ class EnrollVerbHandler extends AbstractVerbHandler {
   /// and its corresponding state.
   ///
   /// Throws "AtEnrollmentException", if the OTP provided is invalid.
+  /// Throws [AtThrottleLimitExceeded], if the number of requests exceed within
+  /// a time window.
   Future<void> _handleEnrollmentRequest(
       EnrollParams enrollParams,
       currentAtSign,
       Map<dynamic, dynamic> responseJson,
       InboundConnection atConnection) async {
+    if (!atConnection.isRequestAllowed()) {
+      throw AtThrottleLimitExceeded(
+          'Enrollment requests have exceeded the limit within the specified time frame');
+    }
     if (!atConnection.getMetaData().isAuthenticated) {
       var otp = enrollParams.otp;
       if (otp == null ||
@@ -119,7 +136,9 @@ class EnrollVerbHandler extends AbstractVerbHandler {
         enrollParams.appName!,
         enrollParams.deviceName!,
         enrollParams.apkamPublicKey!);
-
+    enrollmentValue.namespaces = enrollNamespaces;
+    enrollmentValue.requestType = EnrollRequestType.newEnrollment;
+    AtData enrollData;
     if (atConnection.getMetaData().authType != null &&
         atConnection.getMetaData().authType == AuthType.cram) {
       // auto approve request from connection that is CRAM authenticated.
@@ -137,15 +156,19 @@ class EnrollVerbHandler extends AbstractVerbHandler {
       // The keys with AT_PKAM_PUBLIC_KEY does not sync to client.
       await keyStore.put(
           AT_PKAM_PUBLIC_KEY, AtData()..data = enrollParams.apkamPublicKey!);
+      enrollData = AtData()..data = jsonEncode(enrollmentValue.toJson());
     } else {
       enrollmentValue.approval = EnrollApproval(EnrollStatus.pending.name);
       await _storeNotification(key, enrollParams, currentAtSign);
       responseJson['status'] = 'pending';
+      enrollData = AtData()
+        ..data = jsonEncode(enrollmentValue.toJson())
+        // Set TTL to the pending enrollments.
+        // The enrollments will expire after configured
+        // expiry limit, beyond which any action (approve/deny/revoke) on an
+        // enrollment is forbidden
+        ..metaData = (AtMetaData()..ttl = enrollmentExpiryInMills);
     }
-
-    enrollmentValue.namespaces = enrollNamespaces;
-    enrollmentValue.requestType = EnrollRequestType.newEnrollment;
-    AtData enrollData = AtData()..data = jsonEncode(enrollmentValue.toJson());
     logger.finer('enrollData: $enrollData');
     await keyStore.put('$key$currentAtSign', enrollData, skipCommit: true);
   }
@@ -158,41 +181,64 @@ class EnrollVerbHandler extends AbstractVerbHandler {
       EnrollParams enrollParams,
       currentAtSign,
       String? operation,
-      Map<dynamic, dynamic> responseJson) async {
+      Map<dynamic, dynamic> responseJson,
+      Response response) async {
     final enrollmentIdFromParams = enrollParams.enrollmentId;
-    var key =
+    String enrollmentKey =
         '$enrollmentIdFromParams.$newEnrollmentKeyPattern.$enrollManageNamespace';
-    logger.finer('key: $key$currentAtSign');
-    var enrollData;
+    logger.finer(
+        'Enrollment key: $enrollmentKey$currentAtSign | Enrollment operation: $operation');
+    EnrollDataStoreValue? enrollDataStoreValue;
+    EnrollStatus? enrollStatus;
+    // Fetch and returns enrollment data from the keystore.
+    // Throw AtEnrollmentException, IF
+    //   1. Enrollment key is not present in keystore
+    //   2. Enrollment key is not active
     try {
-      enrollData = await keyStore.get('$key$currentAtSign');
+      enrollDataStoreValue =
+          await getEnrollDataStoreValue('$enrollmentKey$currentAtSign');
     } on KeyNotFoundException {
-      throw AtEnrollmentException(
-          'enrollment id: $enrollmentIdFromParams not found in keystore');
+      // When an enrollment key is expired or invalid
+      enrollStatus = EnrollStatus.expired;
+    }
+    enrollStatus ??=
+        getEnrollStatusFromString(enrollDataStoreValue!.approval!.state);
+    // Validates if enrollment is not expired
+    if (EnrollStatus.expired == enrollStatus) {
+      response.isError = true;
+      response.errorCode = 'AT0028';
+      response.errorMessage =
+          'enrollment_id: $enrollmentIdFromParams is expired or invalid';
     }
-    if (enrollData != null) {
-      final existingAtData = enrollData.data;
-      var enrollDataStoreValue =
-          EnrollDataStoreValue.fromJson(jsonDecode(existingAtData));
+    if (response.isError) {
+      return;
+    }
+    // Verifies whether the enrollment state matches the intended state
+    // Throws AtEnrollmentException, if the enrollment state is different from
+    // the intended state
+    _verifyEnrollmentStateBeforeAction(operation, enrollStatus);
+    enrollDataStoreValue!.approval!.state =
+        _getEnrollStatusEnum(operation).name;
+    responseJson['status'] = _getEnrollStatusEnum(operation).name;
 
-      enrollDataStoreValue.approval!.state =
-          _getEnrollStatusEnum(operation).name;
-      responseJson['status'] = _getEnrollStatusEnum(operation).name;
-      AtData updatedEnrollData = AtData()
-        ..data = jsonEncode(enrollDataStoreValue.toJson());
-      await keyStore.put('$key$currentAtSign', updatedEnrollData,
-          skipCommit: true);
-      // when enrollment is approved store the apkamPublicKey of the enrollment
-      if (operation == 'approve') {
-        var apkamPublicKeyInKeyStore =
-            'public:${enrollDataStoreValue.appName}.${enrollDataStoreValue.deviceName}.pkam.$pkamNamespace.__public_keys$currentAtSign';
-        var valueJson = {};
-        valueJson[apkamPublicKey] = enrollDataStoreValue.apkamPublicKey;
-        var atData = AtData()..data = jsonEncode(valueJson);
-        await keyStore.put(apkamPublicKeyInKeyStore, atData);
-        await _storeEncryptionKeys(
-            enrollmentIdFromParams!, enrollParams, currentAtSign);
-      }
+    // If an enrollment is approved, we need the enrollment to be active
+    // to subsequently revoke the enrollment. Hence reset TTL and
+    // expiredAt on metadata.
+    /* TODO: Currently TTL is reset on all the enrollments.
+        However, if the enrollment state is denied or revoked,
+        unless we wanted to display denied or revoked enrollments in the UI,
+        we can let the TTL be, so that the enrollment will be deleted subsequently.*/
+    await _updateEnrollmentValueAndResetTTL(
+        '$enrollmentKey$currentAtSign', enrollDataStoreValue);
+    // when enrollment is approved store the apkamPublicKey of the enrollment
+    if (operation == 'approve') {
+      var apkamPublicKeyInKeyStore =
+          'public:${enrollDataStoreValue.appName}.${enrollDataStoreValue.deviceName}.pkam.$pkamNamespace.__public_keys$currentAtSign';
+      var valueJson = {'apkamPublicKey': enrollDataStoreValue.apkamPublicKey};
+      var atData = AtData()..data = jsonEncode(valueJson);
+      await keyStore.put(apkamPublicKeyInKeyStore, atData);
+      await _storeEncryptionKeys(
+          enrollmentIdFromParams!, enrollParams, currentAtSign);
     }
     responseJson['enrollmentId'] = enrollmentIdFromParams;
   }
@@ -255,11 +301,13 @@ class EnrollVerbHandler extends AbstractVerbHandler {
     if (_doesEnrollmentHaveManageNamespace(enrollDataStoreValue)) {
       await _fetchAllEnrollments(enrollmentKeysList, enrollmentRequestsMap);
     } else {
-      enrollmentRequestsMap[enrollmentKey] = {
-        'appName': enrollDataStoreValue.appName,
-        'deviceName': enrollDataStoreValue.deviceName,
-        'namespace': enrollDataStoreValue.namespaces
-      };
+      if (enrollDataStoreValue.approval!.state != EnrollStatus.expired.name) {
+        enrollmentRequestsMap[enrollmentKey] = {
+          'appName': enrollDataStoreValue.appName,
+          'deviceName': enrollDataStoreValue.deviceName,
+          'namespace': enrollDataStoreValue.namespaces
+        };
+      }
     }
     return jsonEncode(enrollmentRequestsMap);
   }
@@ -269,11 +317,13 @@ class EnrollVerbHandler extends AbstractVerbHandler {
     for (var enrollmentKey in enrollmentKeysList) {
       EnrollDataStoreValue enrollDataStoreValue =
           await getEnrollDataStoreValue(enrollmentKey);
-      enrollmentRequestsMap[enrollmentKey] = {
-        'appName': enrollDataStoreValue.appName,
-        'deviceName': enrollDataStoreValue.deviceName,
-        'namespace': enrollDataStoreValue.namespaces
-      };
+      if (enrollDataStoreValue.approval!.state != EnrollStatus.expired.name) {
+        enrollmentRequestsMap[enrollmentKey] = {
+          'appName': enrollDataStoreValue.appName,
+          'deviceName': enrollDataStoreValue.deviceName,
+          'namespace': enrollDataStoreValue.namespaces
+        };
+      }
     }
   }
 
@@ -311,4 +361,35 @@ class EnrollVerbHandler extends AbstractVerbHandler {
           'Error while storing notification key $enrollmentId. Error $e. Trace $trace');
     }
   }
+
+  /// Verifies whether the enrollment state matches the intended state.
+  /// Throws AtEnrollmentException: If the enrollment state is different
+  /// from the intended state.
+  void _verifyEnrollmentStateBeforeAction(
+      String? operation, EnrollStatus enrollStatus) {
+    if (operation == 'approve' && EnrollStatus.pending != enrollStatus) {
+      throw AtEnrollmentException(
+          'Cannot approve a ${enrollStatus.name} enrollment. Only pending enrollments can be approved');
+    }
+    if (operation == 'revoke' && EnrollStatus.approved != enrollStatus) {
+      throw AtEnrollmentException(
+          'Cannot revoke a ${enrollStatus.name} enrollment. Only approved enrollments can be revoked');
+    }
+  }
+
+  Future<void> _updateEnrollmentValueAndResetTTL(
+      String enrollmentKey, EnrollDataStoreValue enrollDataStoreValue) async {
+    // Fetch the existing data
+    AtMetaData? enrollMetaData = await keyStore.getMeta(enrollmentKey);
+    // Update key with new data
+    // only update ttl, expiresAt in metadata to preserve all the other valid data fields
+    enrollMetaData?.ttl = 0;
+    enrollMetaData?.expiresAt = null;
+    await keyStore.put(
+        enrollmentKey,
+        AtData()
+          ..data = jsonEncode(enrollDataStoreValue.toJson())
+          ..metaData = enrollMetaData,
+        skipCommit: true);
+  }
 }
diff --git a/packages/at_secondary_server/lib/src/verb/handler/pkam_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/pkam_verb_handler.dart
index 601c977c1..96b28aae2 100644
--- a/packages/at_secondary_server/lib/src/verb/handler/pkam_verb_handler.dart
+++ b/packages/at_secondary_server/lib/src/verb/handler/pkam_verb_handler.dart
@@ -86,16 +86,17 @@ class PkamVerbHandler extends AbstractVerbHandler {
       String enrollId, String atSign) async {
     String enrollmentKey =
         '$enrollId.$newEnrollmentKeyPattern.$enrollManageNamespace$atSign';
-    var enrollData = await keyStore.get(enrollmentKey);
-    final atData = enrollData.data;
-    final enrollDataStoreValue =
-        EnrollDataStoreValue.fromJson(jsonDecode(atData));
-    EnrollStatus enrollStatus =
-        EnrollStatus.values.byName(enrollDataStoreValue.approval!.state);
-
+    late final EnrollDataStoreValue enrollDataStoreValue;
     ApkamVerificationResult apkamResult = ApkamVerificationResult();
-    apkamResult.response = _getApprovalStatus(
-        enrollStatus, enrollId, enrollDataStoreValue.approval!.state);
+    EnrollStatus? enrollStatus;
+    try {
+      enrollDataStoreValue = await getEnrollDataStoreValue(enrollmentKey);
+      enrollStatus = getEnrollStatusFromString(enrollDataStoreValue.approval!.state);
+    } on KeyNotFoundException catch (e) {
+      logger.finer('Caught exception trying to fetch enrollment key: $e');
+      enrollStatus = EnrollStatus.expired;
+    }
+    apkamResult.response = _getApprovalStatus(enrollStatus, enrollId);
     if (apkamResult.response.isError) {
       return apkamResult;
     }
@@ -103,8 +104,7 @@ class PkamVerbHandler extends AbstractVerbHandler {
     return apkamResult;
   }
 
-  Response _getApprovalStatus(
-      EnrollStatus enrollStatus, enrollId, approvalState) {
+  Response _getApprovalStatus(EnrollStatus enrollStatus, enrollId) {
     Response response = Response();
     switch (enrollStatus) {
       case EnrollStatus.denied:
@@ -125,6 +125,11 @@ class PkamVerbHandler extends AbstractVerbHandler {
         response.errorCode = 'AT0027';
         response.errorMessage = 'enrollment_id: $enrollId is revoked';
         break;
+      case EnrollStatus.expired:
+        response.isError = true;
+        response.errorCode = 'AT0028';
+        response.errorMessage = 'enrollment_id: $enrollId is expired or invalid';
+        break;
       default:
         response.isError = true;
         response.errorCode = 'AT0026';
@@ -143,7 +148,7 @@ class PkamVerbHandler extends AbstractVerbHandler {
     bool isValidSignature = false;
     var storedSecret = await keyStore.get('private:$sessionId$atSign');
     storedSecret = storedSecret?.data;
-    if(signature == null || signature.isEmpty ){
+    if (signature == null || signature.isEmpty) {
       logger.severe('inputSignature is null/empty');
       return false;
     }
diff --git a/packages/at_secondary_server/pubspec.yaml b/packages/at_secondary_server/pubspec.yaml
index 5569e42c6..03be29b74 100644
--- a/packages/at_secondary_server/pubspec.yaml
+++ b/packages/at_secondary_server/pubspec.yaml
@@ -1,6 +1,6 @@
 name: at_secondary
 description: Implementation of secondary server.
-version: 3.0.35
+version: 3.0.36
 repository: https://github.com/atsign-foundation/at_server
 homepage: https://www.example.com
 publish_to: none
@@ -18,18 +18,18 @@ dependencies:
   collection: 1.18.0
   basic_utils: 5.6.1
   ecdsa: 0.0.4
-  at_commons: 3.0.54
+  at_commons: 3.0.55
   at_utils: 3.0.15
   at_chops: 1.0.4
   at_lookup: 3.0.40
-  at_server_spec: 3.0.14
+  at_server_spec: 3.0.15
   at_persistence_spec: 2.0.14
   at_persistence_secondary_server: 3.0.57
   expire_cache: ^2.0.1
   intl: ^0.18.1
   json_annotation: ^4.8.0
   version: 3.0.2
-  meta: 1.9.1
+  meta: 1.10.0
   mutex: 3.0.1
   yaml: 3.1.2
   logging: 1.2.0
diff --git a/packages/at_secondary_server/test/enroll_verb_test.dart b/packages/at_secondary_server/test/enroll_verb_test.dart
index 04f0efb1b..6e48d943d 100644
--- a/packages/at_secondary_server/test/enroll_verb_test.dart
+++ b/packages/at_secondary_server/test/enroll_verb_test.dart
@@ -220,7 +220,6 @@ void main() {
     var enrollOperationMap = {
       'approve': 'approved',
       'deny': 'denied',
-      'revoke': 'revoked'
     };
 
     enrollOperationMap.forEach((operation, expectedStatus) {
@@ -342,7 +341,9 @@ void main() {
     test(
         'A test to verify revoke operations thrown exception when given enrollmentId is not in keystore',
         () async {
-      String enrollmentRequest = 'enroll:revoke:{"enrollmentId":"123"}';
+      String enrollmentId = '123';
+      String enrollmentRequest =
+          'enroll:revoke:{"enrollmentId":"$enrollmentId"}';
       HashMap<String, String?> verbParams =
           getVerbParam(VerbSyntax.enroll, enrollmentRequest);
       inboundConnection.getMetaData().isAuthenticated = true;
@@ -352,12 +353,13 @@ void main() {
       Response response = Response();
       EnrollVerbHandler enrollVerbHandler =
           EnrollVerbHandler(secondaryKeyStore);
-      expect(
-          () async => await enrollVerbHandler.processVerb(
-              response, verbParams, inboundConnection),
-          throwsA(predicate((dynamic e) =>
-              e is AtEnrollmentException &&
-              e.message == 'enrollment id: 123 not found in keystore')));
+      await enrollVerbHandler.processVerb(
+          response, verbParams, inboundConnection);
+      expect(response.isError, true);
+      expect(response.errorMessage, isNotNull);
+      assert(response.errorMessage!
+          .contains('enrollment_id: $enrollmentId is expired'));
+      expect(response.errorCode, 'AT0028');
     });
     tearDown(() async => await verbTestsTearDown());
   });
@@ -379,12 +381,12 @@ void main() {
       inboundConnection.getMetaData().sessionID = 'dummy_session';
       (inboundConnection.getMetaData() as InboundConnectionMetadata)
           .enrollmentId = '123';
-      Response response = Response();
+      Response responseObject = Response();
       EnrollVerbHandler enrollVerbHandler =
           EnrollVerbHandler(secondaryKeyStore);
       await enrollVerbHandler.processVerb(
-          response, verbParams, inboundConnection);
-      Map<String, dynamic> enrollmentResponse = jsonDecode(response.data!);
+          responseObject, verbParams, inboundConnection);
+      Map<String, dynamic> enrollmentResponse = jsonDecode(responseObject.data!);
       expect(enrollmentResponse['enrollmentId'], isNotNull);
       expect(enrollmentResponse['status'], 'approved');
       // Commit log
@@ -463,4 +465,262 @@ void main() {
     });
     tearDown(() async => await verbTestsTearDown());
   });
+
+  group('A group of tests related to enrollment request expiry', () {
+    String? otp;
+    setUp(() async {
+      await verbTestsSetUp();
+      // Fetch TOTP
+      String totpCommand = 'otp:get';
+      HashMap<String, String?> totpVerbParams =
+          getVerbParam(VerbSyntax.otp, totpCommand);
+      OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore);
+      inboundConnection.getMetaData().isAuthenticated = true;
+      Response defaultResponse = Response();
+      await otpVerbHandler.processVerb(
+          defaultResponse, totpVerbParams, inboundConnection);
+      otp = defaultResponse.data;
+    });
+    test('A test to verify expired enrollment cannot be approved', () async {
+      Response response = Response();
+      // Enroll a request on an unauthenticated connection which will expire in 1 millisecond
+      EnrollVerbHandler enrollVerbHandler =
+          EnrollVerbHandler(secondaryKeyStore);
+      enrollVerbHandler.enrollmentExpiryInMills = 1;
+      String enrollmentRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"$otp","apkamPublicKey":"dummy_apkam_public_key"}';
+      HashMap<String, String?> enrollVerbParams =
+          getVerbParam(VerbSyntax.enroll, enrollmentRequest);
+      inboundConnection.getMetaData().isAuthenticated = false;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id';
+      await enrollVerbHandler.processVerb(
+          response, enrollVerbParams, inboundConnection);
+      String enrollmentId = jsonDecode(response.data!)['enrollmentId'];
+      String status = jsonDecode(response.data!)['status'];
+      expect(status, 'pending');
+      await Future.delayed(Duration(milliseconds: 1));
+      //Approve enrollment
+      String approveEnrollmentCommand =
+          'enroll:approve:{"enrollmentId":"$enrollmentId"}';
+      enrollVerbParams =
+          getVerbParam(VerbSyntax.enroll, approveEnrollmentCommand);
+      inboundConnection.getMetaData().isAuthenticated = true;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id';
+      await enrollVerbHandler.processVerb(
+          response, enrollVerbParams, inboundConnection);
+      expect(response.isError, true);
+      expect(response.errorMessage, isNotNull);
+      assert(response.errorMessage!
+          .contains('enrollment_id: $enrollmentId is expired'));
+      expect(response.errorCode, 'AT0028');
+    });
+
+    test('A test to verify expired enrollment cannot be denied', () async {
+      Response response = Response();
+      // Enroll a request on an unauthenticated connection which will expire in 1 millisecond
+      EnrollVerbHandler enrollVerbHandler =
+          EnrollVerbHandler(secondaryKeyStore);
+      enrollVerbHandler.enrollmentExpiryInMills = 1;
+      String enrollmentRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"$otp","apkamPublicKey":"dummy_apkam_public_key"}';
+      HashMap<String, String?> enrollVerbParams =
+          getVerbParam(VerbSyntax.enroll, enrollmentRequest);
+      inboundConnection.getMetaData().isAuthenticated = false;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id1';
+      await enrollVerbHandler.processVerb(
+          response, enrollVerbParams, inboundConnection);
+      String enrollmentId = jsonDecode(response.data!)['enrollmentId'];
+      String status = jsonDecode(response.data!)['status'];
+      expect(status, 'pending');
+      //Deny enrollment
+      await Future.delayed(Duration(milliseconds: 1));
+      String approveEnrollmentCommand =
+          'enroll:deny:{"enrollmentId":"$enrollmentId"}';
+      enrollVerbParams =
+          getVerbParam(VerbSyntax.enroll, approveEnrollmentCommand);
+      inboundConnection.getMetaData().isAuthenticated = true;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id';
+      await enrollVerbHandler.processVerb(
+          response, enrollVerbParams, inboundConnection);
+      expect(response.isError, true);
+      expect(response.errorMessage, isNotNull);
+      assert(response.errorMessage!
+          .contains('enrollment_id: $enrollmentId is expired'));
+      expect(response.errorCode, 'AT0028');
+    });
+
+    test('A test to verify TTL on approved enrollment is reset', () async {
+      Response response = Response();
+      // Enroll a request on an unauthenticated connection which will expire in 1 minute
+      EnrollVerbHandler enrollVerbHandler =
+          EnrollVerbHandler(secondaryKeyStore);
+      enrollVerbHandler.enrollmentExpiryInMills = 600000;
+      String enrollmentRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"$otp","apkamPublicKey":"dummy_apkam_public_key"}';
+      HashMap<String, String?> enrollVerbParams =
+          getVerbParam(VerbSyntax.enroll, enrollmentRequest);
+      inboundConnection.getMetaData().isAuthenticated = false;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id';
+      await enrollVerbHandler.processVerb(
+          response, enrollVerbParams, inboundConnection);
+      String enrollmentId = jsonDecode(response.data!)['enrollmentId'];
+      String status = jsonDecode(response.data!)['status'];
+      expect(status, 'pending');
+      String enrollmentKey =
+          '$enrollmentId.$newEnrollmentKeyPattern.$enrollManageNamespace$alice';
+      // Verify TTL is added to the enrollment
+      AtData? enrollmentData = await secondaryKeyStore.get(enrollmentKey);
+      expect(enrollmentData!.metaData!.expiresAt, isNotNull);
+      expect(enrollmentData.metaData!.ttl, 600000);
+      //Approve enrollment
+      String approveEnrollmentCommand =
+          'enroll:approve:{"enrollmentId":"$enrollmentId"}';
+      enrollVerbParams =
+          getVerbParam(VerbSyntax.enroll, approveEnrollmentCommand);
+      inboundConnection.getMetaData().isAuthenticated = true;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id';
+      await enrollVerbHandler.processVerb(
+          response, enrollVerbParams, inboundConnection);
+      // Verify TTL is reset
+      enrollmentData = await secondaryKeyStore.get(enrollmentKey);
+      expect(enrollmentData!.metaData!.expiresAt, null);
+      expect(enrollmentData.metaData!.ttl, 0);
+    });
+
+    test(
+        'A test to verify TTL is not set for enrollment requested on an authenticated connection',
+        () async {
+          Response response = Response();
+      EnrollVerbHandler enrollVerbHandler =
+          EnrollVerbHandler(secondaryKeyStore);
+      String enrollmentRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"$otp","apkamPublicKey":"dummy_apkam_public_key"}';
+      HashMap<String, String?> enrollVerbParams =
+          getVerbParam(VerbSyntax.enroll, enrollmentRequest);
+      inboundConnection.getMetaData().isAuthenticated = true;
+      inboundConnection.getMetaData().authType = AuthType.cram;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id';
+      await enrollVerbHandler.processVerb(
+          response, enrollVerbParams, inboundConnection);
+      String enrollmentId = jsonDecode(response.data!)['enrollmentId'];
+      expect(enrollmentId, isNotNull);
+      expect(jsonDecode(response.data!)['status'], 'approved');
+      // Verify TTL is not set
+      AtData? enrollmentData = await secondaryKeyStore.get(
+          '$enrollmentId.$newEnrollmentKeyPattern.$enrollManageNamespace$alice');
+      expect(enrollmentData!.metaData!.expiresAt, null);
+      expect(enrollmentData.metaData!.ttl, null);
+    });
+    tearDown(() async => await verbTestsTearDown());
+  });
+
+  group('A group of tests related to approve enrollment', () {
+    String? otp;
+    late String enrollmentId;
+    late EnrollVerbHandler enrollVerbHandler;
+    HashMap<String, String?> enrollVerbParams;
+    Response defaultResponse = Response();
+    setUp(() async {
+      await verbTestsSetUp();
+      // Fetch OTP
+      String totpCommand = 'otp:get';
+      HashMap<String, String?> totpVerbParams =
+          getVerbParam(VerbSyntax.otp, totpCommand);
+      OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore);
+      inboundConnection.getMetaData().isAuthenticated = true;
+      await otpVerbHandler.processVerb(
+          defaultResponse, totpVerbParams, inboundConnection);
+      otp = defaultResponse.data;
+      // Enroll a request on an unauthenticated connection which will expire in 1 minute
+      enrollVerbHandler = EnrollVerbHandler(secondaryKeyStore);
+      enrollVerbHandler.enrollmentExpiryInMills = 60000;
+      String enrollmentRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"$otp","apkamPublicKey":"dummy_apkam_public_key"}';
+      HashMap<String, String?> enrollVerbParams =
+          getVerbParam(VerbSyntax.enroll, enrollmentRequest);
+      inboundConnection.getMetaData().isAuthenticated = false;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id';
+      await enrollVerbHandler.processVerb(
+          defaultResponse, enrollVerbParams, inboundConnection);
+      enrollmentId = jsonDecode(defaultResponse.data!)['enrollmentId'];
+      String status = jsonDecode(defaultResponse.data!)['status'];
+      expect(status, 'pending');
+    });
+
+    test('A test to verify denied enrollment cannot be approved', () async {
+      Response response = Response();
+      //deny enrollment
+      String denyEnrollmentCommand =
+          'enroll:deny:{"enrollmentId":"$enrollmentId"}';
+      enrollVerbParams = getVerbParam(VerbSyntax.enroll, denyEnrollmentCommand);
+      inboundConnection.getMetaData().isAuthenticated = true;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id';
+      await enrollVerbHandler.processVerb(
+          response, enrollVerbParams, inboundConnection);
+      expect(jsonDecode(response.data!)['enrollmentId'], enrollmentId);
+      expect(jsonDecode(response.data!)['status'], 'denied');
+      //approve enrollment
+      String approveEnrollmentCommand =
+          'enroll:approve:{"enrollmentId":"$enrollmentId"}';
+      enrollVerbParams =
+          getVerbParam(VerbSyntax.enroll, approveEnrollmentCommand);
+      inboundConnection.getMetaData().isAuthenticated = true;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id';
+      expect(
+          () async => await enrollVerbHandler.processVerb(
+              response, enrollVerbParams, inboundConnection),
+          throwsA(predicate((dynamic e) =>
+              e is AtEnrollmentException &&
+              e.message ==
+                  'Cannot approve a denied enrollment. Only pending enrollments can be approved')));
+    });
+
+    test('A test to verify revoked enrollment cannot be approved', () async {
+      Response response = Response();
+      //approve enrollment
+      String approveEnrollmentCommand =
+          'enroll:approve:{"enrollmentId":"$enrollmentId"}';
+      HashMap<String, String?> approveEnrollVerbParams =
+          getVerbParam(VerbSyntax.enroll, approveEnrollmentCommand);
+      inboundConnection.getMetaData().isAuthenticated = true;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id';
+      await enrollVerbHandler.processVerb(
+          response, approveEnrollVerbParams, inboundConnection);
+      expect(jsonDecode(response.data!)['enrollmentId'], enrollmentId);
+      expect(jsonDecode(response.data!)['status'], 'approved');
+      //revoke enrollment
+      String denyEnrollmentCommand =
+          'enroll:revoke:{"enrollmentId":"$enrollmentId"}';
+      enrollVerbParams = getVerbParam(VerbSyntax.enroll, denyEnrollmentCommand);
+      await enrollVerbHandler.processVerb(
+          response, enrollVerbParams, inboundConnection);
+      expect(jsonDecode(response.data!)['enrollmentId'], enrollmentId);
+      expect(jsonDecode(response.data!)['status'], 'revoked');
+      // Approved a revoked enrollment throws AtEnrollmentException
+      expect(
+          () async => await enrollVerbHandler.processVerb(
+              response, approveEnrollVerbParams, inboundConnection),
+          throwsA(predicate((dynamic e) =>
+              e is AtEnrollmentException &&
+              e.message ==
+                  'Cannot approve a revoked enrollment. Only pending enrollments can be approved')));
+    });
+
+    test('A test to verify pending enrollment cannot be revoked', () async {
+      Response response = Response();
+      //revoke enrollment
+      String denyEnrollmentCommand =
+          'enroll:revoke:{"enrollmentId":"$enrollmentId"}';
+      enrollVerbParams = getVerbParam(VerbSyntax.enroll, denyEnrollmentCommand);
+      inboundConnection.getMetaData().isAuthenticated = true;
+      inboundConnection.getMetaData().sessionID = 'dummy_session_id';
+      expect(
+          () async => await enrollVerbHandler.processVerb(
+              response, enrollVerbParams, inboundConnection),
+          throwsA(predicate((dynamic e) =>
+              e is AtEnrollmentException &&
+              e.message ==
+                  'Cannot revoke a pending enrollment. Only approved enrollments can be revoked')));
+    });
+  });
 }
diff --git a/packages/at_secondary_server/test/inbound_connection_impl_test.dart b/packages/at_secondary_server/test/inbound_connection_impl_test.dart
new file mode 100644
index 000000000..89c2c1d5c
--- /dev/null
+++ b/packages/at_secondary_server/test/inbound_connection_impl_test.dart
@@ -0,0 +1,44 @@
+import 'dart:io';
+
+import 'package:at_secondary/src/connection/inbound/inbound_connection_impl.dart';
+import 'package:at_server_spec/at_server_spec.dart';
+import 'package:test/test.dart';
+
+void main(){
+  group('A test to verify the rate limiter on inbound connection', () {
+    test('A test to verify requests exceeding the limit are rejected', () {
+      Socket? dummySocket;
+      AtConnection connection1 = InboundConnectionImpl(dummySocket, 'aaa');
+      (connection1 as InboundConnectionImpl).maxRequestsPerTimeFrame = 1;
+      connection1.timeFrameInMillis =
+          Duration(milliseconds: 10).inMilliseconds;
+      expect(connection1.isRequestAllowed(), true);
+      expect(connection1.isRequestAllowed(), false);
+    });
+
+    test('A test to verify requests after the time window are accepted',
+            () async {
+          Socket? dummySocket;
+          AtConnection connection1 = InboundConnectionImpl(dummySocket, 'aaa');
+          (connection1 as InboundConnectionImpl).maxRequestsPerTimeFrame = 1;
+          connection1.timeFrameInMillis = Duration(milliseconds: 2).inMilliseconds;
+          expect(connection1.isRequestAllowed(), true);
+          expect(connection1.isRequestAllowed(), false);
+          await Future.delayed(Duration(milliseconds: 2));
+          expect(connection1.isRequestAllowed(), true);
+        });
+
+    test('A test to verify request from different connection is allowed', () {
+      Socket? dummySocket;
+      AtConnection connection1 = InboundConnectionImpl(dummySocket, 'aaa');
+      AtConnection connection2 = InboundConnectionImpl(dummySocket, 'aaa');
+      (connection1 as InboundConnectionImpl).maxRequestsPerTimeFrame = 1;
+      (connection2 as InboundConnectionImpl).maxRequestsPerTimeFrame = 1;
+      connection1.timeFrameInMillis =
+          Duration(milliseconds: 10).inMilliseconds;
+      expect(connection1.isRequestAllowed(), true);
+      expect(connection1.isRequestAllowed(), false);
+      expect(connection2.isRequestAllowed(), true);
+    });
+  });
+}
\ No newline at end of file
diff --git a/packages/at_secondary_server/test/pkam_verb_test.dart b/packages/at_secondary_server/test/pkam_verb_test.dart
index 58f3e89f8..9d93d013e 100644
--- a/packages/at_secondary_server/test/pkam_verb_test.dart
+++ b/packages/at_secondary_server/test/pkam_verb_test.dart
@@ -131,6 +131,18 @@ void main() {
           'enrollment_id: enrollId is denied');
     });
 
+    test('verify apkam behaviour - case: enrollment expired ', () async {
+      enrollData.approval = EnrollApproval('denied');
+      when(() => mockKeyStore.get(any()))
+          .thenThrow(KeyNotFoundException('key not found'));
+
+      var apkamResult =
+      await pkamVerbHandler.handleApkamVerification('enrollId', '@alice');
+      expect(apkamResult.response.isError, true);
+      expect(apkamResult.response.errorCode, 'AT0028');
+      expect(apkamResult.response.errorMessage,
+          'enrollment_id: enrollId is expired or invalid');
+    });
 
     tearDownAll(() async => await tearDownFunc());
   });
diff --git a/packages/at_server_spec/CHANGELOG.md b/packages/at_server_spec/CHANGELOG.md
index d6a3f02ae..cd7e06452 100644
--- a/packages/at_server_spec/CHANGELOG.md
+++ b/packages/at_server_spec/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 3.0.15
+- feat: Introduce AtRateLimiter to limit the requests based on the criteria defined
+- fix: Modify InboundConnection to implement AtRateLimiter to limit requests
 ## 3.0.14
 - fix: Rename TOTP to OTP
 ## 3.0.13
diff --git a/packages/at_server_spec/lib/src/at_rate_limiter/at_rate_limiter.dart b/packages/at_server_spec/lib/src/at_rate_limiter/at_rate_limiter.dart
new file mode 100644
index 000000000..bd2a3d4f3
--- /dev/null
+++ b/packages/at_server_spec/lib/src/at_rate_limiter/at_rate_limiter.dart
@@ -0,0 +1,18 @@
+/// A rate limiter class that allows controlling the rate of requests within a specified time frame.
+///
+/// This class provides a way to limit the number of requests that can be made
+/// within a specified time frame. It keeps track of the timestamps of previous
+/// requests and allows requests to be made only if they do not exceed the
+/// maximum allowed requests per time frame.
+abstract class AtRateLimiter {
+  /// The maximum number of requests allowed within the specified time frame.
+  late int maxRequestsPerTimeFrame;
+
+  /// The duration of the time frame within which requests are limited.
+  late int timeFrameInMillis;
+
+  /// Checks whether a new request is allowed based on the rate limiting rules.
+  ///
+  /// Returns `true` if the request is allowed, or `false` if it exceeds the rate limit.
+  bool isRequestAllowed();
+}
diff --git a/packages/at_server_spec/lib/src/connection/inbound_connection.dart b/packages/at_server_spec/lib/src/connection/inbound_connection.dart
index c4851ab47..bab07d14b 100644
--- a/packages/at_server_spec/lib/src/connection/inbound_connection.dart
+++ b/packages/at_server_spec/lib/src/connection/inbound_connection.dart
@@ -1,7 +1,8 @@
 import 'dart:io';
+import 'package:at_server_spec/src/at_rate_limiter/at_rate_limiter.dart';
 import 'package:at_server_spec/src/connection/at_connection.dart';
 
-abstract class InboundConnection extends AtConnection {
+abstract class InboundConnection extends AtConnection implements AtRateLimiter {
   ///Returns true if remote socket and remote port of this and connection matches
   bool equals(InboundConnection connection);
 
diff --git a/packages/at_server_spec/pubspec.yaml b/packages/at_server_spec/pubspec.yaml
index 9a327f72d..0812c17ab 100644
--- a/packages/at_server_spec/pubspec.yaml
+++ b/packages/at_server_spec/pubspec.yaml
@@ -1,6 +1,6 @@
 name: at_server_spec
 description: A Dart library containing abstract classes that defines what implementations of the root and secondary servers are responsible for.
-version: 3.0.14
+version: 3.0.15
 repository: https://github.com/atsign-foundation/at_server
 homepage: https://docs.atsign.com
 documentation: https://docs.atsign.com/atplatform/secondaryserver
@@ -10,7 +10,7 @@ environment:
 
 dependencies:
   meta: ^1.8.0
-  at_commons: ^3.0.54
+  at_commons: ^3.0.55
 
 dev_dependencies:
   lints: ^1.0.1
diff --git a/tests/at_end2end_test/test/notify_verb_test.dart b/tests/at_end2end_test/test/notify_verb_test.dart
index 383d94cc7..b112a28d3 100644
--- a/tests/at_end2end_test/test/notify_verb_test.dart
+++ b/tests/at_end2end_test/test/notify_verb_test.dart
@@ -49,7 +49,7 @@ void main() {
     String response = await sh2.read();
     print('notify verb response : $response');
     assert(
-        (!response.contains('Invalid syntax')) && (!response.contains('null')));
+    (!response.contains('Invalid syntax')) && (!response.contains('null')));
     String notificationId = response.replaceAll('data:', '');
 
     // notify status
@@ -69,37 +69,37 @@ void main() {
   });
 
   test('test to verify notify fetch verb for a valid notification-id',
-      () async {
-    /// NOTIFY VERB
-    var value = 'Copenhagen';
-    await sh1.writeCommand(
-        'notify:update:messageType:key:notifier:system:ttr:-1:$atSign_2:city.me$atSign_1:$value');
-    String response = await sh1.read();
-    print('notify verb response : $response');
-    assert(
+          () async {
+        /// NOTIFY VERB
+        var value = 'Copenhagen';
+        await sh1.writeCommand(
+            'notify:update:messageType:key:notifier:system:ttr:-1:$atSign_2:city.me$atSign_1:$value');
+        String response = await sh1.read();
+        print('notify verb response : $response');
+        assert(
         (!response.contains('Invalid syntax')) && (!response.contains('null')));
-    String notificationId = response.replaceAll('data:', '');
-
-    // Assert notification status
-    response = await getNotifyStatus(sh1, notificationId,
-        returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
-    print('notify status response : $response');
-    expect(response, contains('data:delivered'));
-
-    // Fetch notification
-    if (atSign1ServerVersion > Version(3, 0, 23)) {
-      await sh1.writeCommand('notify:fetch:$notificationId');
-      response = await sh1.read();
-      response = response.replaceFirst('data:', '');
-      var atNotificationMap = jsonDecode(response);
-      expect(atNotificationMap['id'], notificationId);
-      expect(atNotificationMap['fromAtSign'], atSign_1);
-      expect(atNotificationMap['toAtSign'], atSign_2);
-      expect(atNotificationMap['type'], 'NotificationType.sent');
-      expect(atNotificationMap['notificationStatus'],
-          'NotificationStatus.delivered');
-    }
-  });
+        String notificationId = response.replaceAll('data:', '');
+
+        // Assert notification status
+        response = await getNotifyStatus(sh1, notificationId,
+            returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
+        print('notify status response : $response');
+        expect(response, contains('data:delivered'));
+
+        // Fetch notification
+        if (atSign1ServerVersion > Version(3, 0, 23)) {
+          await sh1.writeCommand('notify:fetch:$notificationId');
+          response = await sh1.read();
+          response = response.replaceFirst('data:', '');
+          var atNotificationMap = jsonDecode(response);
+          expect(atNotificationMap['id'], notificationId);
+          expect(atNotificationMap['fromAtSign'], atSign_1);
+          expect(atNotificationMap['toAtSign'], atSign_2);
+          expect(atNotificationMap['type'], 'NotificationType.sent');
+          expect(atNotificationMap['notificationStatus'],
+              'NotificationStatus.delivered');
+        }
+      });
 
   test('notify verb without messageType and operation', () async {
     /// NOTIFY VERB
@@ -108,7 +108,7 @@ void main() {
     String response = await sh2.read();
     print('notify verb response : $response');
     assert(
-        (!response.contains('Invalid syntax')) && (!response.contains('null')));
+    (!response.contains('Invalid syntax')) && (!response.contains('null')));
     String notificationId = response.replaceAll('data:', '');
 
     // notify status
@@ -118,11 +118,13 @@ void main() {
     assert(response.contains('data:delivered'));
 
     ///notify:list verb
-    await sh1.writeCommand('notify:list');
-    response = await sh1.read();
-    print('notify list verb response : $response');
-    expect(response,
-        contains('"key":"$atSign_1:contact-no$atSign_2","value":null'));
+    if (atSign2ServerVersion > Version(3, 0, 35)) {
+      await sh1.writeCommand('notify:list');
+      response = await sh1.read();
+      print('notify list verb response : $response');
+      expect(response,
+          contains('"key":"$atSign_1:contact-no$atSign_2","value":"$value'));
+    }
   });
 
   test('notify verb without messageType', () async {
@@ -133,7 +135,7 @@ void main() {
     String response = await sh2.read();
     print('notify verb response : $response');
     assert(
-        (!response.contains('Invalid syntax')) && (!response.contains('null')));
+    (!response.contains('Invalid syntax')) && (!response.contains('null')));
     String notificationId = response.replaceAll('data:', '');
 
     // notify status
@@ -161,7 +163,7 @@ void main() {
     print('notify verb response : $response');
     String notificationId = response.replaceAll('data:', '');
     assert(
-        (!response.contains('Invalid syntax')) && (!response.contains('null')));
+    (!response.contains('Invalid syntax')) && (!response.contains('null')));
 
     // notify status
     response = await getNotifyStatus(sh2, notificationId,
@@ -185,7 +187,7 @@ void main() {
     print('notify verb response : $response');
     String notificationId = response.replaceAll('data:', '');
     assert(
-        (!response.contains('Invalid syntax')) && (!response.contains('null')));
+    (!response.contains('Invalid syntax')) && (!response.contains('null')));
 
     //  notify status
     response = await getNotifyStatus(sh1, notificationId,
@@ -194,13 +196,16 @@ void main() {
     expect(response, contains('data:delivered'));
 
     //notify:list verb with regex
+
     await sh2.writeCommand('notify:list:email');
     response = await sh2.read();
     print('notify list verb response : $response');
-    expect(
-        response,
-        contains(
-            '"key":"$atSign_2:email$atSign_1","value":"null","operation":"delete"'));
+    if (atSign1ServerVersion > Version(3,0,35)) {
+      expect(
+          response,
+          contains(
+              '"key":"$atSign_2:email$atSign_1","value":null,"operation":"delete"'));
+    }
   });
 
   test('notify verb without giving message type value', () async {
@@ -234,7 +239,7 @@ void main() {
     print('notify verb response : $response');
     String notificationId = response.replaceAll('data:', '');
     assert(
-        (!response.contains('Invalid syntax')) && (!response.contains('null')));
+    (!response.contains('Invalid syntax')) && (!response.contains('null')));
 
     // notify status
     response = await getNotifyStatus(sh1, notificationId,
@@ -252,7 +257,7 @@ void main() {
     print('notify verb response : $response');
     String notificationId = response.replaceAll('data:', '');
     assert(
-        (!response.contains('Invalid syntax')) && (!response.contains('null')));
+    (!response.contains('Invalid syntax')) && (!response.contains('null')));
 
     // notify status
     response = await getNotifyStatus(sh1, notificationId,
@@ -268,7 +273,7 @@ void main() {
     print('notify verb response : $response');
     String notificationId = response.replaceAll('data:', '');
     assert(
-        (!response.contains('Invalid syntax')) && (!response.contains('null')));
+    (!response.contains('Invalid syntax')) && (!response.contains('null')));
 
     // notify status
     response = await getNotifyStatus(sh1, notificationId,
@@ -319,7 +324,7 @@ void main() {
     String response = await sh1.read();
     print('notify verb response : $response');
     assert(
-        (!response.contains('Invalid syntax')) && (!response.contains('null')));
+    (!response.contains('Invalid syntax')) && (!response.contains('null')));
 
     /// atSign2: notify:list verb with regex
     String shouldContain = '"key":"$atSign_2:whatsapp$atSign_1"';
@@ -362,9 +367,11 @@ void main() {
     print('notify verb response : $response');
     String notificationId = response.replaceAll('data:', '');
     assert(
-        (!response.contains('Invalid syntax')) && (!response.contains('null')));
+    (!response.contains('Invalid syntax')) && (!response.contains('null')));
 
-    int willExpireAt = DateTime.now().millisecondsSinceEpoch + ttln;
+    int willExpireAt = DateTime
+        .now()
+        .millisecondsSinceEpoch + ttln;
 
     //   // notify status before ttln expiry time
     response = await getNotifyStatus(sh2, notificationId,
@@ -373,7 +380,9 @@ void main() {
     expect(response, contains('data:delivered'));
 
     // Wait until ttln has been reached
-    int now = DateTime.now().millisecondsSinceEpoch;
+    int now = DateTime
+        .now()
+        .millisecondsSinceEpoch;
     if (now < willExpireAt) {
       await Future.delayed(Duration(milliseconds: willExpireAt - now));
     }
@@ -386,36 +395,40 @@ void main() {
   });
 
   test('notify verb with notification expiry for errored- invalid atsign',
-      () async {
-    //   /// NOTIFY VERB
-    int ttln = 11000;
-    await sh2.writeCommand(
-        'notify:update:messageType:key:ttln:$ttln:ttr:-1:@xyz:message$atSign_2:Hey!');
-    String response = await sh2.read();
-    print('notify verb response : $response');
-    String notificationId = response.replaceAll('data:', '');
-    assert(
+          () async {
+        //   /// NOTIFY VERB
+        int ttln = 11000;
+        await sh2.writeCommand(
+            'notify:update:messageType:key:ttln:$ttln:ttr:-1:@xyz:message$atSign_2:Hey!');
+        String response = await sh2.read();
+        print('notify verb response : $response');
+        String notificationId = response.replaceAll('data:', '');
+        assert(
         (!response.contains('Invalid syntax')) && (!response.contains('null')));
 
-    int willExpireAt = DateTime.now().millisecondsSinceEpoch + ttln;
-
-    // notify status before ttln expiry time
-    response = await getNotifyStatus(sh2, notificationId,
-        returnWhenStatusIn: ['errored'], timeOutMillis: 10000);
-    expect(response, contains('data:errored'));
-
-    // Wait until ttln has been reached
-    int now = DateTime.now().millisecondsSinceEpoch;
-    if (now < willExpireAt) {
-      await Future.delayed(Duration(milliseconds: willExpireAt - now));
-    }
-
-    /// notify status after ttln expiry time
-    response = await getNotifyStatus(sh2, notificationId,
-        returnWhenStatusIn: ['expired'], timeOutMillis: 5000);
-    print('notify status response : $response');
-    expect(response, contains('data:expired'));
-  });
+        int willExpireAt = DateTime
+            .now()
+            .millisecondsSinceEpoch + ttln;
+
+        // notify status before ttln expiry time
+        response = await getNotifyStatus(sh2, notificationId,
+            returnWhenStatusIn: ['errored'], timeOutMillis: 10000);
+        expect(response, contains('data:errored'));
+
+        // Wait until ttln has been reached
+        int now = DateTime
+            .now()
+            .millisecondsSinceEpoch;
+        if (now < willExpireAt) {
+          await Future.delayed(Duration(milliseconds: willExpireAt - now));
+        }
+
+        /// notify status after ttln expiry time
+        response = await getNotifyStatus(sh2, notificationId,
+            returnWhenStatusIn: ['expired'], timeOutMillis: 5000);
+        print('notify status response : $response');
+        expect(response, contains('data:expired'));
+      });
 
   test('notify verb with notification expiry with messageType text', () async {
     //   /// NOTIFY VERB
@@ -426,9 +439,11 @@ void main() {
     print('notify verb response : $response');
     String notificationId = response.replaceAll('data:', '');
     assert(
-        (!response.contains('Invalid syntax')) && (!response.contains('null')));
+    (!response.contains('Invalid syntax')) && (!response.contains('null')));
 
-    int willExpireAt = DateTime.now().millisecondsSinceEpoch + ttln;
+    int willExpireAt = DateTime
+        .now()
+        .millisecondsSinceEpoch + ttln;
 
     // notify status before ttln expiry time
     response = await getNotifyStatus(sh2, notificationId,
@@ -437,7 +452,9 @@ void main() {
     expect(response, contains('data:delivered'));
 
     // Wait until ttln has been reached
-    int now = DateTime.now().millisecondsSinceEpoch;
+    int now = DateTime
+        .now()
+        .millisecondsSinceEpoch;
     if (now < willExpireAt) {
       await Future.delayed(Duration(milliseconds: willExpireAt - now));
     }
@@ -451,17 +468,17 @@ void main() {
 
   ///
   test('notify verb with notification expiry in an incorrect spelling',
-      () async {
-    //   /// NOTIFY VERB
-    await sh2.writeCommand(
-        'notify:update:ttlnn:5000:ttr:-1:$atSign_1:message$atSign_2:Hey!');
-    String response = await sh2.read();
-    print('notify verb response : $response');
-    expect(response, contains('Invalid syntax'));
-    // Invalid syntax results in a closed connection so let's do some housekeeping
-    sh2.close();
-    sh2 = await e2e.getSocketHandler(atSign_2);
-  });
+          () async {
+        //   /// NOTIFY VERB
+        await sh2.writeCommand(
+            'notify:update:ttlnn:5000:ttr:-1:$atSign_1:message$atSign_2:Hey!');
+        String response = await sh2.read();
+        print('notify verb response : $response');
+        expect(response, contains('Invalid syntax'));
+        // Invalid syntax results in a closed connection so let's do some housekeeping
+        sh2.close();
+        sh2 = await e2e.getSocketHandler(atSign_2);
+      });
 
   test('Test to verify the update and delete caching of key', () async {
     var key = 'testcachedkey-$lastValue';
@@ -534,30 +551,34 @@ void main() {
 
   test(
       'notify a key and verifying the time taken for the status to be delivered',
-      () async {
-    var timeBeforeNotification = DateTime.now().millisecondsSinceEpoch;
-    await sh1.writeCommand(
-        'notify:update:messageType:key:$atSign_2:company$atSign_1:atsign');
-    String response = await sh1.read();
-    print('notify verb response : $response');
-    assert(
+          () async {
+        var timeBeforeNotification = DateTime
+            .now()
+            .millisecondsSinceEpoch;
+        await sh1.writeCommand(
+            'notify:update:messageType:key:$atSign_2:company$atSign_1:atsign');
+        String response = await sh1.read();
+        print('notify verb response : $response');
+        assert(
         (!response.contains('Invalid syntax')) && (!response.contains('null')));
-    String notificationId = response.replaceAll('data:', '');
-    assert(
+        String notificationId = response.replaceAll('data:', '');
+        assert(
         (!response.contains('Invalid syntax')) && (!response.contains('null')));
 
-    // notify status
-    response = await getNotifyStatus(sh1, notificationId,
-        returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
-    print('notify status response : $response');
-    expect(response, contains('data:delivered'));
-    var timeAfterNotification = DateTime.now().millisecondsSinceEpoch;
-    var timeDifferenceValue =
+        // notify status
+        response = await getNotifyStatus(sh1, notificationId,
+            returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
+        print('notify status response : $response');
+        expect(response, contains('data:delivered'));
+        var timeAfterNotification = DateTime
+            .now()
+            .millisecondsSinceEpoch;
+        var timeDifferenceValue =
         DateTime.fromMillisecondsSinceEpoch(timeAfterNotification).difference(
             DateTime.fromMillisecondsSinceEpoch(timeBeforeNotification));
-    print('time difference is $timeDifferenceValue');
-    expect(timeDifferenceValue.inMilliseconds <= 10000, true);
-  });
+        print('time difference is $timeDifferenceValue');
+        expect(timeDifferenceValue.inMilliseconds <= 10000, true);
+      });
 
   /// The purpose of this test is verify the date time of second notification is correct
   /// not picked from the earlier notification.
@@ -576,7 +597,8 @@ void main() {
     var notificationIdFromAtSign2 = (await sh2.read()).replaceAll('data:', '');
     var atNotificationMap = jsonDecode(notificationIdFromAtSign2);
     var firstNotificationDateInEpoch =
-        DateTime.parse(atNotificationMap['notificationDateTime'])
+        DateTime
+            .parse(atNotificationMap['notificationDateTime'])
             .microsecondsSinceEpoch;
 
     // Sending second notification
@@ -593,63 +615,64 @@ void main() {
     notificationIdFromAtSign2 = (await sh2.read()).replaceAll('data:', '');
     atNotificationMap = jsonDecode(notificationIdFromAtSign2);
     var secondNotificationDateInEpoch =
-        DateTime.parse(atNotificationMap['notificationDateTime'])
+        DateTime
+            .parse(atNotificationMap['notificationDateTime'])
             .microsecondsSinceEpoch;
 
     expect(secondNotificationDateInEpoch > firstNotificationDateInEpoch, true);
   });
 
   test('notify verb for notifying a key update with shared key metadata',
-      () async {
-    /// NOTIFY VERB
-    await sh1.writeCommand('notify:update:messageType:key:notifier:SYSTEM'
-        ':ttln:86400000:ttr:60000:ccd:false'
-        ':sharedKeyEnc:abc:pubKeyCS:3c55db695d94b304827367a4f5cab8ae'
-        ':$atSign_2:phone.wavi$atSign_1:Some ciphertext');
-    String response = await sh1.read();
-    print('notify verb response : $response');
-    assert(
+          () async {
+        /// NOTIFY VERB
+        await sh1.writeCommand('notify:update:messageType:key:notifier:SYSTEM'
+            ':ttln:86400000:ttr:60000:ccd:false'
+            ':sharedKeyEnc:abc:pubKeyCS:3c55db695d94b304827367a4f5cab8ae'
+            ':$atSign_2:phone.wavi$atSign_1:Some ciphertext');
+        String response = await sh1.read();
+        print('notify verb response : $response');
+        assert(
         (!response.contains('Invalid syntax')) && (!response.contains('null')));
-    String notificationId = response.replaceAll('data:', '');
-
-    // notify status
-    response = await getNotifyStatus(sh1, notificationId,
-        returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
-    print('notify status response : $response');
-    expect(response, contains('data:delivered'));
-
-    await sh2.writeCommand('llookup:all:cached:$atSign_2:phone.wavi$atSign_1');
-    response = await sh2.read();
-    response = response.replaceAll('data:', '');
-    var decodedResponse = jsonDecode(response);
-    expect(decodedResponse['key'], 'cached:$atSign_2:phone.wavi$atSign_1');
-    expect(decodedResponse['data'], 'Some ciphertext');
-    expect(decodedResponse['metaData']['sharedKeyEnc'], 'abc');
-    expect(decodedResponse['metaData']['pubKeyCS'],
-        '3c55db695d94b304827367a4f5cab8ae');
-    expect(decodedResponse['metaData']['ttr'], 60000);
-  });
+        String notificationId = response.replaceAll('data:', '');
+
+        // notify status
+        response = await getNotifyStatus(sh1, notificationId,
+            returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
+        print('notify status response : $response');
+        expect(response, contains('data:delivered'));
+
+        await sh2.writeCommand(
+            'llookup:all:cached:$atSign_2:phone.wavi$atSign_1');
+        response = await sh2.read();
+        response = response.replaceAll('data:', '');
+        var decodedResponse = jsonDecode(response);
+        expect(decodedResponse['key'], 'cached:$atSign_2:phone.wavi$atSign_1');
+        expect(decodedResponse['data'], 'Some ciphertext');
+        expect(decodedResponse['metaData']['sharedKeyEnc'], 'abc');
+        expect(decodedResponse['metaData']['pubKeyCS'],
+            '3c55db695d94b304827367a4f5cab8ae');
+        expect(decodedResponse['metaData']['ttr'], 60000);
+      });
 
   test('notify verb for notifying a key update with new encryption metadata',
-      () async {
-    /// NOTIFY VERB
-    var sharedKeyEnc = 'abc';
-    var pubKeyCS = '3c55db695d94b304827367a4f5cab8ae';
-    var encKeyName = 'someEncKeyName';
-    var encAlgo = 'AES/CTR/PKCS7Padding';
-    var iv = 'anInitializationVector';
-    var skeEncKeyName = 'someSkeEncKeyName';
-    var skeEncAlgo = 'RSA-2048';
-    var ttln = 60 * 1000; // 60 seconds
-
-    if (atSign1ServerVersion < Version(3, 0, 29)) {
-      // Server version 3.0.28 or earlier will not process new metadata
-      // No point in trying to send anything
-      return;
-    }
-
-    await sh1.writeCommand(
-        'notify:update'
+          () async {
+        /// NOTIFY VERB
+        var sharedKeyEnc = 'abc';
+        var pubKeyCS = '3c55db695d94b304827367a4f5cab8ae';
+        var encKeyName = 'someEncKeyName';
+        var encAlgo = 'AES/CTR/PKCS7Padding';
+        var iv = 'anInitializationVector';
+        var skeEncKeyName = 'someSkeEncKeyName';
+        var skeEncAlgo = 'RSA-2048';
+        var ttln = 60 * 1000; // 60 seconds
+
+        if (atSign1ServerVersion < Version(3, 0, 29)) {
+          // Server version 3.0.28 or earlier will not process new metadata
+          // No point in trying to send anything
+          return;
+        }
+
+        await sh1.writeCommand('notify:update'
             ':messageType:key'
             ':notifier:SYSTEM'
             ':ttln:$ttln'
@@ -664,47 +687,284 @@ void main() {
             ':skeEncAlgo:$skeEncAlgo'
             ':$atSign_2:phone.wavi$atSign_1'
             ':Some ciphertext');
-    String response = await sh1.read();
-    print('notify verb response : $response');
-    assert(
+        String response = await sh1.read();
+        print('notify verb response : $response');
+        assert(
         (!response.contains('Invalid syntax')) && (!response.contains('null')));
-    String notificationId = response.replaceAll('data:', '');
-
-    // notify status
-    response = await getNotifyStatus(sh1, notificationId,
-        returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
-    print('notify status response : $response');
-    expect(response, contains('data:delivered'));
+        String notificationId = response.replaceAll('data:', '');
+
+        // notify status
+        response = await getNotifyStatus(sh1, notificationId,
+            returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
+        print('notify status response : $response');
+        expect(response, contains('data:delivered'));
+
+        await sh2.writeCommand(
+            'llookup:all:cached:$atSign_2:phone.wavi$atSign_1');
+        response = await sh2.read();
+        response = response.replaceAll('data:', '');
+        var decodedResponse = jsonDecode(response);
+        expect(decodedResponse['key'], 'cached:$atSign_2:phone.wavi$atSign_1');
+        expect(decodedResponse['data'], 'Some ciphertext');
+        expect(decodedResponse['metaData']['sharedKeyEnc'], sharedKeyEnc);
+        expect(decodedResponse['metaData']['pubKeyCS'], pubKeyCS);
+        expect(decodedResponse['metaData']['ttr'], 10);
+
+        if (atSign2ServerVersion > Version(3, 0, 28)) {
+          expect(decodedResponse['metaData']['encKeyName'], encKeyName);
+          expect(decodedResponse['metaData']['encAlgo'], encAlgo);
+          expect(decodedResponse['metaData']['ivNonce'], iv);
+          expect(decodedResponse['metaData']['skeEncKeyName'], skeEncKeyName);
+          expect(decodedResponse['metaData']['skeEncAlgo'], skeEncAlgo);
+        } else {
+          expect(decodedResponse['metaData']['encKeyName'], null);
+          expect(decodedResponse['metaData']['encAlgo'], null);
+          expect(decodedResponse['metaData']['ivNonce'], null);
+          expect(decodedResponse['metaData']['skeEncKeyName'], null);
+          expect(decodedResponse['metaData']['skeEncAlgo'], null);
+        }
+      });
+
+  group('A group of tests related to notify ephemeral', () {
+    test(
+        'notify verb without ttr for messageType-key and operation type - update and with value',
+            () async {
+          // The notify ephemeral changes are not into Canary and production.
+          // So, no point in running against and Canary and Prod servers.`
+          if (atSign2ServerVersion < Version(3, 0, 36)) {
+            return;
+          }
+
+          /// NOTIFY VERB
+          var value = 'testingvalue';
+          await sh2.writeCommand(
+              'notify:update:messageType:key:$atSign_1:testkey$atSign_2:$value');
+          String response = await sh2.read();
+          print('notify verb response : $response');
+          assert((!response.contains('Invalid syntax')) &&
+              (!response.contains('null')));
+          String notificationId = response.replaceAll('data:', '');
+
+          // notify status
+          response = await getNotifyStatus(sh2, notificationId,
+              returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
+          print('notify status response : $response');
+          assert(response.contains('data:delivered'));
+
+          ///notify:list verb
+          await sh1.writeCommand('notify:list');
+          response = await sh1.read();
+          print('notify list verb response : $response');
+          expect(
+              response,
+              contains(
+                  '"key":"$atSign_1:testkey$atSign_2","value":"$value","operation":"update"'));
+        });
+
+    test('notify verb without ttr and without value for operation type update',
+            () async {
+          // The notify ephemeral changes are not into Canary and production.
+          // So, no point in running against and Canary and Prod servers.`
+          if (atSign2ServerVersion < Version(3, 0, 36)) {
+            return;
+          }
+
+          /// NOTIFY VERB
+          await sh2.writeCommand('notify:update:$atSign_1:nottrkey$atSign_2');
+          String response = await sh2.read();
+          print('notify verb response : $response');
+          assert((!response.contains('Invalid syntax')) &&
+              (!response.contains('null')));
+          String notificationId = response.replaceAll('data:', '');
+
+          // notify status
+          response = await getNotifyStatus(sh2, notificationId,
+              returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
+          print('notify status response : $response');
+          assert(response.contains('data:delivered'));
+
+          ///notify:list verb
+          await sh1.writeCommand('notify:list');
+          response = await sh1.read();
+          print('notify list verb response : $response');
+          expect(
+              response,
+              contains(
+                  '"key":"$atSign_1:nottrkey$atSign_2","value":null,"operation":"update"'));
+        });
+
+    test(
+        'notify verb without ttr for messageType-text and operation type - update',
+            () async {
+          // The notify ephemeral changes are not into Canary and production.
+          // So, no point in running against and Canary and Prod servers.`
+          if (atSign2ServerVersion < Version(3, 0, 36)) {
+            return;
+          }
+
+          /// NOTIFY VERB
+          await sh2.writeCommand(
+              'notify:update:messageType:text:$atSign_1:hello_world$atSign_2');
+          String response = await sh2.read();
+          print('notify verb response : $response');
+          assert((!response.contains('Invalid syntax')) &&
+              (!response.contains('null')));
+          String notificationId = response.replaceAll('data:', '');
+
+          // notify status
+          response = await getNotifyStatus(sh2, notificationId,
+              returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
+          print('notify status response : $response');
+          assert(response.contains('data:delivered'));
+
+          ///notify:list verb
+          await sh1.writeCommand('notify:list');
+          response = await sh1.read();
+          print('notify list verb response : $response');
+          expect(
+              response,
+              contains(
+                  '"key":"$atSign_1:hello_world","value":null,"operation":"update"'));
+        });
+
+    test(
+        'notify verb without ttr for messageType-text and operation type - delete',
+            () async {
+          // The notify ephemeral changes are not into Canary and production.
+          // So, no point in running against and Canary and Prod servers.`
+          if (atSign2ServerVersion < Version(3, 0, 36)) {
+            return;
+          }
+
+          /// NOTIFY VERB
+          await sh2.writeCommand(
+              'notify:delete:messageType:text:$atSign_1:hello_world$atSign_2');
+          String response = await sh2.read();
+          print('notify verb response : $response');
+          assert((!response.contains('Invalid syntax')) &&
+              (!response.contains('null')));
+          String notificationId = response.replaceAll('data:', '');
+
+          // notify status
+          response = await getNotifyStatus(sh2, notificationId,
+              returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
+          print('notify status response : $response');
+          assert(response.contains('data:delivered'));
+
+          ///notify:list verb
+          await sh1.writeCommand('notify:list');
+          response = await sh1.read();
+          print('notify list verb response : $response');
+          expect(
+              response,
+              contains(
+                  '"key":"$atSign_1:hello_world","value":null,"operation":"delete"'));
+        });
+
+    test(
+        'notify verb without ttr and without value for operation type update (self notification)',
+            () async {
+          // The notify ephemeral changes are not into Canary and production.
+          // So, no point in running against and Canary and Prod servers.`
+          if (atSign2ServerVersion < Version(3, 0, 36)) {
+            return;
+          }
+
+          /// NOTIFY VERB
+          await sh2.writeCommand('notify:update:$atSign_2:nottrkey$atSign_2');
+          String response = await sh2.read();
+          print('notify verb response : $response');
+          assert((!response.contains('Invalid syntax')) &&
+              (!response.contains('null')));
+          String notificationId = response.replaceAll('data:', '');
+
+          // notify status
+          response = await getNotifyStatus(sh2, notificationId,
+              returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
+          print('notify status response : $response');
+          assert(response.contains('data:delivered'));
+
+          ///notify:list verb
+          await sh2.writeCommand('notify:list');
+          response = await sh2.read();
+          print('notify list verb response : $response');
+          expect(
+              response,
+              contains(
+                  '"key":"$atSign_2:nottrkey$atSign_2","value":null,"operation":"update"'));
+        });
+
+    test(
+        'notify verb without ttr and with value for operation type update (self notification)',
+            () async {
+          // The notify ephemeral changes are not into Canary and production.
+          // So, no point in running against and Canary and Prod servers.`
+          if (atSign2ServerVersion < Version(3, 0, 36)) {
+            return;
+          }
+
+          /// NOTIFY VERB
+          var value = 'no-ttr';
+          await sh2
+              .writeCommand('notify:update:$atSign_2:nottrkey$atSign_2:$value');
+          String response = await sh2.read();
+          print('notify verb response : $response');
+          assert((!response.contains('Invalid syntax')) &&
+              (!response.contains('null')));
+          String notificationId = response.replaceAll('data:', '');
+
+          // notify status
+          response = await getNotifyStatus(sh2, notificationId,
+              returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
+          print('notify status response : $response');
+          assert(response.contains('data:delivered'));
+
+          ///notify:list verb
+          await sh2.writeCommand('notify:list');
+          response = await sh2.read();
+          print('notify list verb response : $response');
+          expect(
+              response,
+              contains(
+                  '"key":"$atSign_2:nottrkey$atSign_2","value":"$value","operation":"update"'));
+        });
+
+    test('notify verb without ttr for operation type delete', () async {
+      // The notify ephemeral changes are not into Canary and production.
+      // So, no point in running against and Canary and Prod servers.`
+      if (atSign2ServerVersion < Version(3, 0, 36)) {
+        return;
+      }
 
-    await sh2.writeCommand('llookup:all:cached:$atSign_2:phone.wavi$atSign_1');
-    response = await sh2.read();
-    response = response.replaceAll('data:', '');
-    var decodedResponse = jsonDecode(response);
-    expect(decodedResponse['key'], 'cached:$atSign_2:phone.wavi$atSign_1');
-    expect(decodedResponse['data'], 'Some ciphertext');
-    expect(decodedResponse['metaData']['sharedKeyEnc'], sharedKeyEnc);
-    expect(decodedResponse['metaData']['pubKeyCS'], pubKeyCS);
-    expect(decodedResponse['metaData']['ttr'], 10);
-
-    if (atSign2ServerVersion > Version(3, 0, 28)) {
-      expect(decodedResponse['metaData']['encKeyName'], encKeyName);
-      expect(decodedResponse['metaData']['encAlgo'], encAlgo);
-      expect(decodedResponse['metaData']['ivNonce'], iv);
-      expect(decodedResponse['metaData']['skeEncKeyName'], skeEncKeyName);
-      expect(decodedResponse['metaData']['skeEncAlgo'], skeEncAlgo);
-    } else {
-      expect(decodedResponse['metaData']['encKeyName'], null);
-      expect(decodedResponse['metaData']['encAlgo'], null);
-      expect(decodedResponse['metaData']['ivNonce'], null);
-      expect(decodedResponse['metaData']['skeEncKeyName'], null);
-      expect(decodedResponse['metaData']['skeEncAlgo'], null);
-    }
+      /// NOTIFY VERB
+      await sh2.writeCommand('notify:delete:$atSign_1:twitter-id$atSign_2');
+      String response = await sh2.read();
+      print('notify verb response : $response');
+      assert((!response.contains('Invalid syntax')) &&
+          (!response.contains('null')));
+      String notificationId = response.replaceAll('data:', '');
+
+      // notify status
+      response = await getNotifyStatus(sh2, notificationId,
+          returnWhenStatusIn: ['delivered'], timeOutMillis: 15000);
+      print('notify status response : $response');
+      assert(response.contains('data:delivered'));
+
+      ///notify:list verb
+      await sh1.writeCommand('notify:list');
+      response = await sh1.read();
+      print('notify list verb response : $response');
+      expect(
+          response,
+          contains(
+              '"key":"$atSign_1:twitter-id$atSign_2","value":null,"operation":"delete"'));
+    });
   });
 }
 
 // get notify status
-Future<String> getNotifyStatus(
-    e2e.SimpleOutboundSocketHandler sh, String notificationId,
+Future<String> getNotifyStatus(e2e.SimpleOutboundSocketHandler sh,
+    String notificationId,
     {List<String>? returnWhenStatusIn, int timeOutMillis = 5000}) async {
   returnWhenStatusIn ??= ['expired'];
   print(
@@ -715,8 +975,12 @@ Future<String> getNotifyStatus(
   String response = 'NO_RESPONSE';
 
   bool readTimedOut = false;
-  int endTime = DateTime.now().millisecondsSinceEpoch + timeOutMillis;
-  while (DateTime.now().millisecondsSinceEpoch < endTime) {
+  int endTime = DateTime
+      .now()
+      .millisecondsSinceEpoch + timeOutMillis;
+  while (DateTime
+      .now()
+      .millisecondsSinceEpoch < endTime) {
     await Future.delayed(Duration(milliseconds: loopDelay));
 
     if (!readTimedOut) {
@@ -726,7 +990,7 @@ Future<String> getNotifyStatus(
         log: true, timeoutMillis: loopDelay, throwTimeoutException: false);
 
     readTimedOut =
-        (response == e2e.SimpleOutboundSocketHandler.readTimedOutMessage);
+    (response == e2e.SimpleOutboundSocketHandler.readTimedOutMessage);
 
     if (response.startsWith('data:')) {
       String status = response.replaceFirst('data:', '').replaceAll('\n', '');
@@ -752,8 +1016,12 @@ Future<String> retryCommandUntilMatchOrTimeout(
   String response = 'NO_RESPONSE';
 
   bool readTimedOut = false;
-  int endTime = DateTime.now().millisecondsSinceEpoch + timeoutMillis;
-  while (DateTime.now().millisecondsSinceEpoch < endTime) {
+  int endTime = DateTime
+      .now()
+      .millisecondsSinceEpoch + timeoutMillis;
+  while (DateTime
+      .now()
+      .millisecondsSinceEpoch < endTime) {
     await Future.delayed(Duration(milliseconds: loopDelay));
 
     if (!readTimedOut) {
@@ -764,7 +1032,7 @@ Future<String> retryCommandUntilMatchOrTimeout(
         log: false, timeoutMillis: loopDelay, throwTimeoutException: false);
 
     readTimedOut =
-        (response == e2e.SimpleOutboundSocketHandler.readTimedOutMessage);
+    (response == e2e.SimpleOutboundSocketHandler.readTimedOutMessage);
     if (readTimedOut) {
       continue;
     }
diff --git a/tests/at_functional_test/pubspec.yaml b/tests/at_functional_test/pubspec.yaml
index f688db31a..e0a4919e0 100644
--- a/tests/at_functional_test/pubspec.yaml
+++ b/tests/at_functional_test/pubspec.yaml
@@ -16,7 +16,7 @@ dependencies:
       ref: trunk
   at_chops: ^1.0.1
   at_lookup: ^3.0.32
-  at_commons: ^3.0.53
+  at_commons: ^3.0.55
   uuid: ^3.0.7
   elliptic: ^0.3.8
 
diff --git a/tests/at_functional_test/test/enroll_verb_test.dart b/tests/at_functional_test/test/enroll_verb_test.dart
index b7eac899e..afa4e56db 100644
--- a/tests/at_functional_test/test/enroll_verb_test.dart
+++ b/tests/at_functional_test/test/enroll_verb_test.dart
@@ -119,10 +119,8 @@ void main() {
       await socket_writer(socketConnection1!, approveEnrollCommand);
       var approveEnrollResponse = await read();
       approveEnrollResponse = approveEnrollResponse.replaceFirst('error:', '');
-      expect(
-          approveEnrollResponse.contains(
-              'enrollment id: $dummyEnrollmentId not found in keystore'),
-          true);
+      expect(approveEnrollResponse,
+          'AT0028:enrollment_id: $dummyEnrollmentId is expired or invalid\n');
     });
 
     test(
@@ -141,10 +139,8 @@ void main() {
       await socket_writer(socketConnection1!, denyEnrollCommand);
       var denyEnrollResponse = await read();
       denyEnrollResponse = denyEnrollResponse.replaceFirst('error:', '');
-      expect(
-          denyEnrollResponse.contains(
-              'enrollment id: $dummyEnrollmentId not found in keystore'),
-          true);
+      expect(denyEnrollResponse,
+          'AT0028:enrollment_id: $dummyEnrollmentId is expired or invalid\n');
     });
 
     test('enroll request on unauthenticated connection without otp', () async {
@@ -651,4 +647,230 @@ void main() {
       });
     });
   });
+
+  group('A group of negative tests on enroll verb', () {
+    late String enrollmentId;
+    late String enrollmentResponse;
+    setUp(() async {
+      // Get TOTP from server
+      String otp = await _getOTPFromServer(firstAtsign);
+      await socketConnection1?.close();
+      // Close the connection and create a new connection and send an enrollment request on an
+      // unauthenticated connection.
+      await _connect();
+      String enrollRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}';
+      await socket_writer(socketConnection1!, enrollRequest);
+      enrollmentResponse = await read();
+      enrollmentResponse = enrollmentResponse.replaceAll('data:', '');
+      enrollmentId = jsonDecode(enrollmentResponse)['enrollmentId'];
+      socketConnection1?.close();
+    });
+    test(
+        'A test to verify error is returned when pending enrollment is revoked',
+        () async {
+      // Revoke enrollment on an authenticate connection
+      await _connect();
+      await prepare(socketConnection1!, firstAtsign);
+      await socket_writer(
+          socketConnection1!, 'enroll:revoke:{"enrollmentId":"$enrollmentId"}');
+      enrollmentResponse = (await read()).replaceAll('error:', '');
+      expect(jsonDecode(enrollmentResponse)['errorDescription'],
+          'Internal server exception : Cannot revoke a pending enrollment. Only approved enrollments can be revoked');
+    });
+
+    test(
+        'A test to verify error is returned when denied enrollment is approved',
+        () async {
+      // Deny enrollment on an authenticate connection
+      await _connect();
+      await prepare(socketConnection1!, firstAtsign);
+      await socket_writer(
+          socketConnection1!, 'enroll:deny:{"enrollmentId":"$enrollmentId"}');
+      enrollmentResponse = (await read()).replaceAll('data:', '');
+      expect(jsonDecode(enrollmentResponse)['status'], 'denied');
+      expect(jsonDecode(enrollmentResponse)['enrollmentId'], enrollmentId);
+      // Approve enrollment
+      await socket_writer(socketConnection1!,
+          'enroll:approve:{"enrollmentId":"$enrollmentId"}');
+      enrollmentResponse = (await read()).replaceAll('error:', '');
+      expect(
+          jsonDecode(enrollmentResponse)['errorDescription'],
+          'Internal server exception : Cannot approve a denied enrollment. '
+          'Only pending enrollments can be approved');
+    });
+
+    test('A test to verify error is returned when denied enrollment is revoked',
+        () async {
+      // Deny enrollment on an authenticate connection
+      await _connect();
+      await prepare(socketConnection1!, firstAtsign);
+      await socket_writer(
+          socketConnection1!, 'enroll:deny:{"enrollmentId":"$enrollmentId"}');
+      enrollmentResponse = (await read()).replaceAll('data:', '');
+      expect(jsonDecode(enrollmentResponse)['status'], 'denied');
+      expect(jsonDecode(enrollmentResponse)['enrollmentId'], enrollmentId);
+      // Revoke enrollment
+      await socket_writer(
+          socketConnection1!, 'enroll:revoke:{"enrollmentId":"$enrollmentId"}');
+      enrollmentResponse = (await read()).replaceAll('error:', '');
+      expect(
+          jsonDecode(enrollmentResponse)['errorDescription'],
+          'Internal server exception : Cannot revoke a denied enrollment. '
+          'Only approved enrollments can be revoked');
+    });
+
+    test('A test to verify revoked enrollment cannot be approved', () async {
+      // Approve enrollment
+      await _connect();
+      await prepare(socketConnection1!, firstAtsign);
+      await socket_writer(socketConnection1!,
+          'enroll:approve:{"enrollmentId":"$enrollmentId"}');
+      enrollmentResponse = (await read()).replaceAll('data:', '');
+      expect(jsonDecode(enrollmentResponse)['status'], 'approved');
+      expect(jsonDecode(enrollmentResponse)['enrollmentId'], enrollmentId);
+      // Revoke enrollment
+      await socket_writer(
+          socketConnection1!, 'enroll:revoke:{"enrollmentId":"$enrollmentId"}');
+      enrollmentResponse = (await read()).replaceAll('data:', '');
+      expect(jsonDecode(enrollmentResponse)['status'], 'revoked');
+      expect(jsonDecode(enrollmentResponse)['enrollmentId'], enrollmentId);
+      // Approve a revoked enrollment
+      await socket_writer(socketConnection1!,
+          'enroll:approve:{"enrollmentId":"$enrollmentId"}');
+      enrollmentResponse = (await read()).replaceAll('error:', '');
+      expect(
+          jsonDecode(enrollmentResponse)['errorDescription'],
+          'Internal server exception : Cannot approve a revoked enrollment. '
+          'Only pending enrollments can be approved');
+    });
+  });
+
+  group('A group of test related to Rate limiting enrollment requests', () {
+    String otp = '';
+    setUp(() async {
+      await socket_writer(socketConnection1!, 'from:$firstAtsign');
+      var fromResponse = await read();
+      fromResponse = fromResponse.replaceAll('data:', '');
+      var cramResponse = getDigest(firstAtsign, fromResponse);
+      await socket_writer(socketConnection1!, 'cram:$cramResponse');
+      var cramResult = await read();
+      expect(cramResult, 'data:success\n');
+      await socket_writer(
+          socketConnection1!, 'config:set:maxRequestsPerTimeFrame=1\n');
+      var configResponse = await read();
+      expect(configResponse.trim(), 'data:ok');
+      await socket_writer(
+          socketConnection1!, 'config:set:timeFrameInMills=100\n');
+      configResponse = await read();
+      expect(configResponse.trim(), 'data:ok');
+      await socket_writer(socketConnection1!, 'otp:get');
+      otp = await read();
+      otp = otp.replaceAll('data:', '').trim();
+    });
+
+    test(
+        'A test to verify exception is thrown when request exceed the configured limit',
+        () async {
+      SecureSocket unAuthenticatedConnection =
+          await secure_socket_connection(firstAtsignServer, firstAtsignPort);
+      socket_listener(unAuthenticatedConnection);
+      var enrollRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n';
+      await socket_writer(unAuthenticatedConnection, enrollRequest);
+      var enrollmentResponse =
+          jsonDecode((await read()).replaceAll('data:', ''));
+      expect(enrollmentResponse['status'], 'pending');
+      expect(enrollmentResponse['enrollmentId'], isNotNull);
+      enrollRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n';
+      await socket_writer(unAuthenticatedConnection, enrollRequest);
+      enrollmentResponse = await read()
+        ..replaceAll('error:', '');
+      expect(
+          enrollmentResponse.contains(
+              'Enrollment requests have exceeded the limit within the specified time frame'),
+          true);
+    });
+
+    test('A test to verify request is successful after the time window',
+        () async {
+      SecureSocket unAuthenticatedConnection =
+          await secure_socket_connection(firstAtsignServer, firstAtsignPort);
+      socket_listener(unAuthenticatedConnection);
+      var enrollRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n';
+      await socket_writer(unAuthenticatedConnection, enrollRequest);
+      var enrollmentResponse =
+          jsonDecode((await read()).replaceAll('data:', ''));
+      expect(enrollmentResponse['status'], 'pending');
+      expect(enrollmentResponse['enrollmentId'], isNotNull);
+      enrollRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n';
+      await socket_writer(unAuthenticatedConnection, enrollRequest);
+      enrollmentResponse = await read()
+        ..replaceAll('error:', '');
+      expect(
+          enrollmentResponse.contains(
+              'Enrollment requests have exceeded the limit within the specified time frame'),
+          true);
+      await Future.delayed(Duration(milliseconds: 110));
+      await socket_writer(unAuthenticatedConnection, enrollRequest);
+      enrollmentResponse = jsonDecode((await read()).replaceAll('data:', ''));
+      expect(enrollmentResponse['status'], 'pending');
+      expect(enrollmentResponse['enrollmentId'], isNotNull);
+    });
+
+    test('A test to verify rate limit is per connection', () async {
+      SecureSocket unAuthenticatedConnection =
+          await secure_socket_connection(firstAtsignServer, firstAtsignPort);
+      socket_listener(unAuthenticatedConnection);
+      var enrollRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n';
+      await socket_writer(unAuthenticatedConnection, enrollRequest);
+      var enrollmentResponse =
+          jsonDecode((await read()).replaceAll('data:', ''));
+      expect(enrollmentResponse['status'], 'pending');
+      expect(enrollmentResponse['enrollmentId'], isNotNull);
+      enrollRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n';
+      await socket_writer(unAuthenticatedConnection, enrollRequest);
+      enrollmentResponse = await read()
+        ..replaceAll('error:', '');
+      expect(
+          enrollmentResponse.contains(
+              'Enrollment requests have exceeded the limit within the specified time frame'),
+          true);
+      SecureSocket secondUnAuthenticatedConnection2 =
+          await secure_socket_connection(firstAtsignServer, firstAtsignPort);
+      socket_listener(secondUnAuthenticatedConnection2);
+      enrollRequest =
+          'enroll:request:{"appName":"wavi","deviceName":"pixel","namespaces":{"wavi":"rw"},"otp":"$otp","apkamPublicKey":"${pkamPublicKeyMap[firstAtsign]!}"}\n';
+      await socket_writer(secondUnAuthenticatedConnection2, enrollRequest);
+      enrollmentResponse = jsonDecode((await read()).replaceAll('data:', ''));
+      expect(enrollmentResponse['status'], 'pending');
+      expect(enrollmentResponse['enrollmentId'], isNotNull);
+    });
+
+    tearDown(() async {
+      socket_writer(socketConnection1!, 'config:reset:maxRequestsAllowed');
+      await read();
+      socket_writer(socketConnection1!, 'config:reset:timeWindowInMills');
+      await read();
+    });
+  });
+}
+
+Future<String> _getOTPFromServer(String atSign) async {
+  await socket_writer(socketConnection1!, 'from:$atSign');
+  var fromResponse = await read();
+  fromResponse = fromResponse.replaceAll('data:', '');
+  var pkamDigest = generatePKAMDigest(atSign, fromResponse);
+  await socket_writer(socketConnection1!, 'pkam:$pkamDigest');
+  // Calling read to remove the PKAM request from the queue
+  await read();
+  await socket_writer(socketConnection1!, 'otp:get');
+  String otp = await read();
+  otp = otp.replaceAll('data:', '').trim();
+  return otp;
 }
diff --git a/tools/build_secondary/Dockerfile b/tools/build_secondary/Dockerfile
index b62ccd554..d09b25296 100644
--- a/tools/build_secondary/Dockerfile
+++ b/tools/build_secondary/Dockerfile
@@ -1,4 +1,4 @@
-FROM dart:3.1.0@sha256:96d2e5d03b8356c2a7542716ace7dce745971efe1d03888a1d7ecd2e7c1dde36 AS buildimage
+FROM dart:3.1.1@sha256:ec7bb9e577648ea5526c9daf714e9bc7af670ce7c93b594205e68c14a10cea3b AS buildimage
 ENV HOMEDIR=/atsign
 ENV USER_ID=1024
 ENV GROUP_ID=1024
diff --git a/tools/build_secondary/Dockerfile.observe b/tools/build_secondary/Dockerfile.observe
index 75c53affc..36ae693cd 100644
--- a/tools/build_secondary/Dockerfile.observe
+++ b/tools/build_secondary/Dockerfile.observe
@@ -1,4 +1,4 @@
-FROM dart:3.1.0@sha256:96d2e5d03b8356c2a7542716ace7dce745971efe1d03888a1d7ecd2e7c1dde36 AS buildimage
+FROM dart:3.1.1@sha256:ec7bb9e577648ea5526c9daf714e9bc7af670ce7c93b594205e68c14a10cea3b AS buildimage
 ENV HOMEDIR=/atsign
 ENV USER_ID=1024
 ENV GROUP_ID=1024
diff --git a/tools/build_virtual_environment/ve/Dockerfile.vip b/tools/build_virtual_environment/ve/Dockerfile.vip
index 91b025b19..e402ca057 100644
--- a/tools/build_virtual_environment/ve/Dockerfile.vip
+++ b/tools/build_virtual_environment/ve/Dockerfile.vip
@@ -1,4 +1,4 @@
-FROM dart:3.1.0@sha256:96d2e5d03b8356c2a7542716ace7dce745971efe1d03888a1d7ecd2e7c1dde36 AS buildimage
+FROM dart:3.1.1@sha256:ec7bb9e577648ea5526c9daf714e9bc7af670ce7c93b594205e68c14a10cea3b AS buildimage
 ENV USER_ID=1024
 ENV GROUP_ID=1024
 WORKDIR /app
diff --git a/tools/build_virtual_environment/ve_base/Dockerfile b/tools/build_virtual_environment/ve_base/Dockerfile
index 9131e5f4c..e8553fea4 100644
--- a/tools/build_virtual_environment/ve_base/Dockerfile
+++ b/tools/build_virtual_environment/ve_base/Dockerfile
@@ -1,4 +1,4 @@
-FROM dart:3.1.0@sha256:96d2e5d03b8356c2a7542716ace7dce745971efe1d03888a1d7ecd2e7c1dde36 AS buildimage
+FROM dart:3.1.1@sha256:ec7bb9e577648ea5526c9daf714e9bc7af670ce7c93b594205e68c14a10cea3b AS buildimage
 ENV USER_ID=1024
 ENV GROUP_ID=1024
 WORKDIR /app
@@ -17,7 +17,7 @@ RUN \
   dart pub update ; \
   dart compile exe bin/install_PKAM_Keys.dart -o install_PKAM_Keys
 
-FROM debian:stable-20230814-slim@sha256:6fe30b9cb71d604a872557be086c74f95451fecd939d72afe3cffca3d9e60607
+FROM debian:stable-20230904-slim@sha256:0941f9e9cc96c4106845a381fb6fca98393f5f659f3eba6a64e9f79219165cfc
 # was debian:stable-20221114-slim
 USER root