diff --git a/doc/development/internal_api/index.md b/doc/development/internal_api/index.md
index 93b3c7dec6daa5ed9c1ffdd04b02b71aae74251b..30976a88cf62768d9c341d7cbe30091b7a751f07 100644
--- a/doc/development/internal_api/index.md
+++ b/doc/development/internal_api/index.md
@@ -554,6 +554,46 @@ curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" --header "Con
      --data '{"counters": {"gitops_sync":1}}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics"
 ```
 
+### GitLab agent events
+
+Called from GitLab agent server (`kas`) to track events.
+
+| Attribute                                                                     | Type          | Required | Description                                                               |
+|:------------------------------------------------------------------------------|:--------------|:---------|:--------------------------------------------------------------------------|
+| `events`                                                                      | hash          | no       | Hash of events                                                            |
+| `events["k8s_api_proxy_requests_unique_users_via_ci_access"]`                 | hash array    | no       | Array of events for `k8s_api_proxy_requests_unique_users_via_ci_access`   |
+| `events["k8s_api_proxy_requests_unique_users_via_ci_access"]["user_id"]`      | integer       | no       | The user ID for the event                                                 |
+| `events["k8s_api_proxy_requests_unique_users_via_ci_access"]["project_id"]`   | integer       | no       | The project ID for the event                                              |
+| `events["k8s_api_proxy_requests_unique_users_via_user_access"]`               | hash array    | no       | Array of events for `k8s_api_proxy_requests_unique_users_via_user_access` |
+| `events["k8s_api_proxy_requests_unique_users_via_user_access"]["user_id"]`    | integer       | no       | The user ID for the event                                                 |
+| `events["k8s_api_proxy_requests_unique_users_via_user_access"]["project_id"]` | integer       | no       | The project ID for the event                                              |
+| `events["k8s_api_proxy_requests_unique_users_via_pat_access"]`                | hash array    | no       | Array of events for `k8s_api_proxy_requests_unique_users_via_pat_access`  |
+| `events["k8s_api_proxy_requests_unique_users_via_pat_access"]["user_id"]`     | integer       | no       | The user ID for the event                                                 |
+| `events["k8s_api_proxy_requests_unique_users_via_pat_access"]["project_id"]`  | integer       | no       | The project ID for the event                                              |
+
+```plaintext
+POST /internal/kubernetes/agent_events
+```
+
+Example Request:
+
+```shell
+curl --request POST \
+  --url "http://localhost:3000/api/v4/internal/kubernetes/agent_events" \
+  --header "Gitlab-Kas-Api-Request: <JWT token>" \
+  --header "Content-Type: application/json" \
+  --data '{
+    "events": {
+      "k8s_api_proxy_requests_unique_users_via_ci_access": [
+        {
+          "user_id": 1,
+          "project_id": 1
+        }
+      ]
+    }
+  }'
+```
+
 ### Create Starboard vulnerability
 
 Called from the GitLab agent server (`kas`) to create a security vulnerability
diff --git a/lib/api/helpers/kubernetes/agent_helpers.rb b/lib/api/helpers/kubernetes/agent_helpers.rb
index ff80270fc15eac7c38445250d0274a64cfdf44e1..88fd52ddd2b5c6db9034c8b2bf27fd5396c7f049 100644
--- a/lib/api/helpers/kubernetes/agent_helpers.rb
+++ b/lib/api/helpers/kubernetes/agent_helpers.rb
@@ -54,6 +54,20 @@ def increment_unique_events
           end
         end
 
+        def track_events
+          event_lists = params[:events]&.slice(
+            :k8s_api_proxy_requests_unique_users_via_ci_access,
+            :k8s_api_proxy_requests_unique_users_via_user_access,
+            :k8s_api_proxy_requests_unique_users_via_pat_access
+          )
+          return if event_lists.blank?
+
+          users, projects = load_users_and_projects(event_lists)
+          event_lists.each do |event_name, events|
+            track_events_for(event_name, events, users, projects)
+          end
+        end
+
         def track_unique_user_events
           events = params[:unique_counters]&.slice(
             :k8s_api_proxy_requests_unique_users_via_ci_access,
@@ -130,6 +144,29 @@ def access_token
           PersonalAccessToken.find_by_token(params[:access_key])
         end
         strong_memoize_attr :access_token
+
+        private
+
+        def load_users_and_projects(event_lists)
+          all_events = event_lists.values.flatten
+          unique_user_ids = all_events.pluck('user_id').compact.uniq # rubocop:disable CodeReuse/ActiveRecord -- this pluck isn't from ActiveRecord, it's from ActiveSupport
+          unique_project_ids = all_events.pluck('project_id').compact.uniq # rubocop:disable CodeReuse/ActiveRecord -- this pluck isn't from ActiveRecord, it's from ActiveSupport
+          users = User.id_in(unique_user_ids).index_by(&:id)
+          projects = Project.id_in(unique_project_ids).index_by(&:id)
+          [users, projects]
+        end
+
+        def track_events_for(event_name, events, users, projects)
+          events.each do |event|
+            next if event.blank?
+
+            user = users[event[:user_id]]
+            project = projects[event[:project_id]]
+            next if user.nil? || project.nil?
+
+            Gitlab::InternalEvents.track_event(event_name, user: user, project: project)
+          end
+        end
       end
     end
   end
diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb
index c6538e17b88cdb2a4276c38a547ebb3f58fabbd4..d3a4d94f8cac6d15daf06c8ea2a655192e45197d 100644
--- a/lib/api/internal/kubernetes.rb
+++ b/lib/api/internal/kubernetes.rb
@@ -147,6 +147,33 @@ class Kubernetes < ::API::Base
             bad_request!(e.message)
           end
         end
+
+        namespace 'kubernetes/agent_events' do
+          desc 'POST agent events' do
+            detail 'Updates agent events'
+          end
+          params do
+            optional :events, type: Hash, desc: 'Array of events' do
+              optional :k8s_api_proxy_requests_unique_users_via_ci_access, type: Array, desc: 'An array of events that have interacted with the CI tunnel via `ci_access`' do
+                optional :user_id, type: Integer, desc: 'User ID'
+                optional :project_id, type: Integer, desc: 'Project ID'
+              end
+              optional :k8s_api_proxy_requests_unique_users_via_user_access, type: Array, desc: 'An array of events that have interacted with the CI tunnel via `ci_access`' do
+                optional :user_id, type: Integer, desc: 'User ID'
+                optional :project_id, type: Integer, desc: 'Project ID'
+              end
+              optional :k8s_api_proxy_requests_unique_users_via_pat_access, type: Array, desc: 'An array of events that have interacted with the CI tunnel via `ci_access`' do
+                optional :user_id, type: Integer, desc: 'User ID'
+                optional :project_id, type: Integer, desc: 'Project ID'
+              end
+            end
+          end
+          post '/', feature_category: :deployment_management do
+            track_events
+
+            no_content!
+          end
+        end
       end
     end
   end
diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb
index f16fdcb9cc73a68ecbc4bd6ddc9ddda5b37473d0..1f0e2a079b83e0eec7b76e683856ebcc53054e4a 100644
--- a/spec/requests/api/internal/kubernetes_spec.rb
+++ b/spec/requests/api/internal/kubernetes_spec.rb
@@ -162,6 +162,87 @@ def send_request(headers: {}, params: {})
     end
   end
 
+  describe 'POST /internal/kubernetes/agent_events', :clean_gitlab_redis_shared_state do
+    def send_request(headers: {}, params: {})
+      post api('/internal/kubernetes/agent_events'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
+    end
+
+    include_examples 'authorization'
+    include_examples 'error handling'
+
+    context 'is authenticated for an agent' do
+      let!(:agent_token) { create(:cluster_agent_token) }
+
+      context 'when events are valid' do
+        let(:request_count) { 2 }
+        let(:users) { create_list(:user, 3).index_by(&:id) }
+        let(:projects) { create_list(:project, 3).index_by(&:id) }
+        let(:events) do
+          user_ids = users.keys
+          project_ids = projects.keys
+          event_data = Array.new(3) do |i|
+            { user_id: user_ids[i], project_id: project_ids[i] }
+          end
+          {
+            k8s_api_proxy_requests_unique_users_via_ci_access: event_data,
+            k8s_api_proxy_requests_unique_users_via_user_access: event_data,
+            k8s_api_proxy_requests_unique_users_via_pat_access: event_data
+          }
+        end
+
+        it 'tracks events and returns no_content', :aggregate_failures do
+          events.each do |event_name, event_list|
+            event_list.each do |event|
+              expect(Gitlab::InternalEvents).to receive(:track_event)
+                                                  .with(event_name.to_s, user: users[event[:user_id]], project: projects[event[:project_id]])
+                                                  .exactly(request_count).times
+            end
+          end
+
+          request_count.times do
+            send_request(params: { events: events })
+          end
+
+          expect(response).to have_gitlab_http_status(:no_content)
+        end
+      end
+
+      context 'when events are empty' do
+        let(:events) do
+          {
+            k8s_api_proxy_requests_unique_users_via_ci_access: [],
+            k8s_api_proxy_requests_unique_users_via_user_access: [],
+            k8s_api_proxy_requests_unique_users_via_pat_access: []
+          }
+        end
+
+        it 'returns no_content for empty events' do
+          expect(Gitlab::InternalEvents).not_to receive(:track_event)
+          send_request(params: { events: events })
+
+          expect(response).to have_gitlab_http_status(:no_content)
+        end
+      end
+
+      context 'when events have non-integer values' do
+        let(:events) do
+          {
+            k8s_api_proxy_requests_unique_users_via_ci_access: [
+              { user_id: 'string', project_id: 111 }
+            ]
+          }
+        end
+
+        it 'returns 400 for non-integer values' do
+          expect(Gitlab::InternalEvents).not_to receive(:track_event)
+          send_request(params: { events: events })
+
+          expect(response).to have_gitlab_http_status(:bad_request)
+        end
+      end
+    end
+  end
+
   describe 'POST /internal/kubernetes/agent_configuration' do
     def send_request(headers: {}, params: {})
       post api('/internal/kubernetes/agent_configuration'), params: params, headers: headers.reverse_merge(jwt_auth_headers)