diff --git a/libs/providers/go-feature-flag-web/src/lib/collector-manager.ts b/libs/providers/go-feature-flag-web/src/lib/collector-manager.ts
new file mode 100644
index 000000000..59e6ea5a7
--- /dev/null
+++ b/libs/providers/go-feature-flag-web/src/lib/collector-manager.ts
@@ -0,0 +1,71 @@
+import { Logger } from '@openfeature/web-sdk';
+import { ExporterMetadataValue, FeatureEvent, GoFeatureFlagWebProviderOptions, TrackingEvent } from './model';
+import { GoffApiController } from './controller/goff-api';
+import { CollectorError } from './errors/collector-error';
+import { copy } from 'copy-anything';
+
+type Timer = ReturnType<typeof setInterval>;
+
+export class CollectorManager {
+  // bgSchedulerId contains the id of the setInterval that is running.
+  private bgScheduler?: Timer;
+  // dataCollectorBuffer contains all the FeatureEvents that we need to send to the relay-proxy for data collection.
+  private dataCollectorBuffer?: Array<FeatureEvent<any> | TrackingEvent>;
+  // dataFlushInterval interval time (in millisecond) we use to call the relay proxy to collect data.
+  private readonly dataFlushInterval: number;
+  // logger is the Open Feature logger to use
+  private logger?: Logger;
+  // dataCollectorMetadata are the metadata used when calling the data collector endpoint
+  private readonly dataCollectorMetadata: Record<string, ExporterMetadataValue>;
+
+  private readonly goffApiController: GoffApiController;
+
+  constructor(options: GoFeatureFlagWebProviderOptions, logger?: Logger) {
+    this.dataFlushInterval = options.dataFlushInterval || 1000 * 60;
+    this.logger = logger;
+    this.goffApiController = new GoffApiController(options);
+
+    this.dataCollectorMetadata = {
+      provider: 'web',
+      openfeature: true,
+      ...options.exporterMetadata,
+    };
+  }
+
+  init() {
+    this.bgScheduler = setInterval(async () => await this.callGoffDataCollection(), this.dataFlushInterval);
+    this.dataCollectorBuffer = [];
+  }
+
+  async close() {
+    clearInterval(this.bgScheduler);
+    // We call the data collector with what is still in the buffer.
+    await this.callGoffDataCollection();
+  }
+
+  add(event: FeatureEvent<any> | TrackingEvent) {
+    if (this.dataCollectorBuffer) {
+      this.dataCollectorBuffer.push(event);
+    }
+  }
+
+  /**
+   * callGoffDataCollection is a function called periodically to send the usage of the flag to the
+   * central service in charge of collecting the data.
+   */
+  async callGoffDataCollection() {
+    const dataToSend = copy(this.dataCollectorBuffer) || [];
+    this.dataCollectorBuffer = [];
+    try {
+      await this.goffApiController.collectData(dataToSend, this.dataCollectorMetadata);
+    } catch (e) {
+      if (!(e instanceof CollectorError)) {
+        throw e;
+      }
+      this.logger?.error(e);
+      // if we have an issue calling the collector, we put the data back in the buffer
+      this.dataCollectorBuffer = [...this.dataCollectorBuffer, ...dataToSend];
+      return;
+    }
+  }
+}
diff --git a/libs/providers/go-feature-flag-web/src/lib/controller/goff-api.ts b/libs/providers/go-feature-flag-web/src/lib/controller/goff-api.ts
index d1ce4f922..ed70bbf9b 100644
--- a/libs/providers/go-feature-flag-web/src/lib/controller/goff-api.ts
+++ b/libs/providers/go-feature-flag-web/src/lib/controller/goff-api.ts
@@ -1,4 +1,10 @@
-import { DataCollectorRequest, ExporterMetadataValue, FeatureEvent, GoFeatureFlagWebProviderOptions } from '../model';
+import {
+  DataCollectorRequest,
+  ExporterMetadataValue,
+  FeatureEvent,
+  GoFeatureFlagWebProviderOptions,
+  TrackingEvent,
+} from '../model';
 import { CollectorError } from '../errors/collector-error';
 
 export class GoffApiController {
@@ -15,7 +21,10 @@ export class GoffApiController {
     this.options = options;
   }
 
-  async collectData(events: FeatureEvent<any>[], dataCollectorMetadata: Record<string, ExporterMetadataValue>) {
+  async collectData(
+    events: Array<FeatureEvent<any> | TrackingEvent>,
+    dataCollectorMetadata: Record<string, ExporterMetadataValue>,
+  ) {
     if (events?.length === 0) {
       return;
     }
diff --git a/libs/providers/go-feature-flag-web/src/lib/data-collector-hook.ts b/libs/providers/go-feature-flag-web/src/lib/data-collector-hook.ts
index dccb7dfb5..8ec5f9704 100644
--- a/libs/providers/go-feature-flag-web/src/lib/data-collector-hook.ts
+++ b/libs/providers/go-feature-flag-web/src/lib/data-collector-hook.ts
@@ -1,65 +1,14 @@
-import { EvaluationDetails, FlagValue, Hook, HookContext, Logger } from '@openfeature/web-sdk';
-import { ExporterMetadataValue, FeatureEvent, GoFeatureFlagWebProviderOptions } from './model';
-import { copy } from 'copy-anything';
-import { CollectorError } from './errors/collector-error';
-import { GoffApiController } from './controller/goff-api';
+import { EvaluationDetails, FlagValue, Hook, HookContext } from '@openfeature/web-sdk';
+import { CollectorManager } from './collector-manager';
 
 const defaultTargetingKey = 'undefined-targetingKey';
 type Timer = ReturnType<typeof setInterval>;
 
 export class GoFeatureFlagDataCollectorHook implements Hook {
-  // bgSchedulerId contains the id of the setInterval that is running.
-  private bgScheduler?: Timer;
-  // dataCollectorBuffer contains all the FeatureEvents that we need to send to the relay-proxy for data collection.
-  private dataCollectorBuffer?: FeatureEvent<any>[];
-  // dataFlushInterval interval time (in millisecond) we use to call the relay proxy to collect data.
-  private readonly dataFlushInterval: number;
-  // dataCollectorMetadata are the metadata used when calling the data collector endpoint
-  private readonly dataCollectorMetadata: Record<string, ExporterMetadataValue>;
-  private readonly goffApiController: GoffApiController;
-  // logger is the Open Feature logger to use
-  private logger?: Logger;
+  private collectorManagger?: CollectorManager;
 
-  constructor(options: GoFeatureFlagWebProviderOptions, logger?: Logger) {
-    this.dataFlushInterval = options.dataFlushInterval || 1000 * 60;
-    this.logger = logger;
-    this.goffApiController = new GoffApiController(options);
-    this.dataCollectorMetadata = {
-      provider: 'web',
-      openfeature: true,
-      ...options.exporterMetadata,
-    };
-  }
-
-  init() {
-    this.bgScheduler = setInterval(async () => await this.callGoffDataCollection(), this.dataFlushInterval);
-    this.dataCollectorBuffer = [];
-  }
-
-  async close() {
-    clearInterval(this.bgScheduler);
-    // We call the data collector with what is still in the buffer.
-    await this.callGoffDataCollection();
-  }
-
-  /**
-   * callGoffDataCollection is a function called periodically to send the usage of the flag to the
-   * central service in charge of collecting the data.
-   */
-  async callGoffDataCollection() {
-    const dataToSend = copy(this.dataCollectorBuffer) || [];
-    this.dataCollectorBuffer = [];
-    try {
-      await this.goffApiController.collectData(dataToSend, this.dataCollectorMetadata);
-    } catch (e) {
-      if (!(e instanceof CollectorError)) {
-        throw e;
-      }
-      this.logger?.error(e);
-      // if we have an issue calling the collector, we put the data back in the buffer
-      this.dataCollectorBuffer = [...this.dataCollectorBuffer, ...dataToSend];
-      return;
-    }
+  constructor(collectorManager: CollectorManager) {
+    this.collectorManagger = collectorManager;
   }
 
   after(hookContext: HookContext, evaluationDetails: EvaluationDetails<FlagValue>) {
@@ -74,7 +23,7 @@ export class GoFeatureFlagDataCollectorHook implements Hook {
       userKey: hookContext.context.targetingKey || defaultTargetingKey,
       source: 'PROVIDER_CACHE',
     };
-    this.dataCollectorBuffer?.push(event);
+    this.collectorManagger?.add(event);
   }
 
   error(hookContext: HookContext) {
@@ -89,6 +38,6 @@ export class GoFeatureFlagDataCollectorHook implements Hook {
       userKey: hookContext.context.targetingKey || defaultTargetingKey,
       source: 'PROVIDER_CACHE',
     };
-    this.dataCollectorBuffer?.push(event);
+    this.collectorManagger?.add(event);
   }
 }
diff --git a/libs/providers/go-feature-flag-web/src/lib/go-feature-flag-web-provider.spec.ts b/libs/providers/go-feature-flag-web/src/lib/go-feature-flag-web-provider.spec.ts
index 2ed757c18..03b7de332 100644
--- a/libs/providers/go-feature-flag-web/src/lib/go-feature-flag-web-provider.spec.ts
+++ b/libs/providers/go-feature-flag-web/src/lib/go-feature-flag-web-provider.spec.ts
@@ -10,7 +10,7 @@ import {
 } from '@openfeature/web-sdk';
 import WS from 'jest-websocket-mock';
 import TestLogger from './test-logger';
-import { DataCollectorRequest, GOFeatureFlagWebsocketResponse } from './model';
+import { DataCollectorRequest, GOFeatureFlagWebsocketResponse, TrackingEvent } from './model';
 import fetchMock from 'fetch-mock-jest';
 
 describe('GoFeatureFlagWebProvider', () => {
@@ -456,156 +456,204 @@ describe('GoFeatureFlagWebProvider', () => {
   });
 
   describe('data collector testing', () => {
-    it('should call the data collector when closing Open Feature', async () => {
-      const clientName = expect.getState().currentTestName ?? 'test-provider';
-      await OpenFeature.setContext(defaultContext);
-      const p = new GoFeatureFlagWebProvider(
-        {
-          endpoint: endpoint,
-          apiTimeout: 1000,
-          maxRetries: 1,
-          dataFlushInterval: 10000,
-          apiKey: 'toto',
-        },
-        logger,
-      );
-
-      await OpenFeature.setProviderAndWait(clientName, p);
-      const client = OpenFeature.getClient(clientName);
-      await websocketMockServer.connected;
-      await new Promise((resolve) => setTimeout(resolve, 5));
-
-      client.getBooleanDetails('bool_flag', false);
-      client.getBooleanDetails('bool_flag', false);
-
-      await OpenFeature.close();
-
-      expect(fetchMock.calls(dataCollectorEndpoint).length).toBe(1);
-      expect(fetchMock.lastOptions(dataCollectorEndpoint)?.headers).toEqual({
-        'Content-Type': 'application/json',
-        Accept: 'application/json',
-        Authorization: 'Bearer toto',
+    describe('tracking event', () => {
+      it('should send tracking event to the data collector', async () => {
+        const clientName = expect.getState().currentTestName ?? 'test-provider';
+        await OpenFeature.setContext(defaultContext);
+        const p = new GoFeatureFlagWebProvider(
+          {
+            endpoint: endpoint,
+            apiTimeout: 1000,
+            maxRetries: 1,
+            dataFlushInterval: 10000,
+          },
+          logger,
+        );
+
+        await OpenFeature.setProviderAndWait(clientName, p);
+        const client = OpenFeature.getClient(clientName);
+        await websocketMockServer.connected;
+        await new Promise((resolve) => setTimeout(resolve, 5));
+
+        client.getBooleanDetails('bool_flag', false);
+        client.getBooleanDetails('bool_flag', false);
+        client.track('event-key-123abc', { value: 99.77, currency: 'USD' });
+
+        await OpenFeature.close();
+
+        expect(fetchMock.calls(dataCollectorEndpoint).length).toBe(1);
+        const reqBody = fetchMock.lastOptions(dataCollectorEndpoint)?.body;
+        const parsedBody = JSON.parse(reqBody as never) as DataCollectorRequest<never>;
+        expect(parsedBody.events.length).toBe(3);
+        expect(parsedBody.events.filter((event) => event.kind === 'tracking').length).toBe(1);
+        expect(parsedBody.events.filter((event) => event.kind === 'feature').length).toBe(2);
+
+        const trackingEvent = parsedBody.events.find((event) => event.kind === 'tracking');
+        expect(trackingEvent).not.toBeUndefined();
+        const c = trackingEvent as TrackingEvent;
+        expect(c.key).toEqual('event-key-123abc');
+        expect(c.kind).toEqual('tracking');
+        expect(c.contextKind).toEqual('user');
+        expect(c.userKey).toEqual(defaultContext.targetingKey);
+        expect(c.creationDate).toBeGreaterThan(0);
+        expect(c.evaluationContext).toEqual(defaultContext);
+        expect(c.trackingEventDetails).toEqual({ value: 99.77, currency: 'USD' });
       });
     });
 
-    it('should call the data collector when waiting more than the dataFlushInterval', async () => {
-      const clientName = expect.getState().currentTestName ?? 'test-provider';
-      await OpenFeature.setContext(defaultContext);
-      const p = new GoFeatureFlagWebProvider(
-        {
-          endpoint: endpoint,
-          apiTimeout: 1000,
-          maxRetries: 1,
-          dataFlushInterval: 200,
-        },
-        logger,
-      );
-
-      await OpenFeature.setProviderAndWait(clientName, p);
-      const client = OpenFeature.getClient(clientName);
-      await websocketMockServer.connected;
-      await new Promise((resolve) => setTimeout(resolve, 5));
-
-      client.getBooleanDetails('bool_flag', false);
-      client.getBooleanDetails('bool_flag', false);
-
-      await new Promise((resolve) => setTimeout(resolve, 300));
-
-      expect(fetchMock.calls(dataCollectorEndpoint).length).toBe(1);
-      expect(fetchMock.lastOptions(dataCollectorEndpoint)?.headers).toEqual({
-        'Content-Type': 'application/json',
-        Accept: 'application/json',
+    describe('feature event', () => {
+      it('should call the data collector when closing Open Feature', async () => {
+        const clientName = expect.getState().currentTestName ?? 'test-provider';
+        await OpenFeature.setContext(defaultContext);
+        const p = new GoFeatureFlagWebProvider(
+          {
+            endpoint: endpoint,
+            apiTimeout: 1000,
+            maxRetries: 1,
+            dataFlushInterval: 10000,
+            apiKey: 'toto',
+          },
+          logger,
+        );
+
+        await OpenFeature.setProviderAndWait(clientName, p);
+        const client = OpenFeature.getClient(clientName);
+        await websocketMockServer.connected;
+        await new Promise((resolve) => setTimeout(resolve, 5));
+
+        client.getBooleanDetails('bool_flag', false);
+        client.getBooleanDetails('bool_flag', false);
+
+        await OpenFeature.close();
+
+        expect(fetchMock.calls(dataCollectorEndpoint).length).toBe(1);
+        expect(fetchMock.lastOptions(dataCollectorEndpoint)?.headers).toEqual({
+          'Content-Type': 'application/json',
+          Accept: 'application/json',
+          Authorization: 'Bearer toto',
+        });
       });
-      await OpenFeature.close();
-    });
-    it('should call the data collector multiple time while waiting dataFlushInterval time', async () => {
-      const clientName = expect.getState().currentTestName ?? 'test-provider';
-      await OpenFeature.setContext(defaultContext);
-      const p = new GoFeatureFlagWebProvider(
-        {
-          endpoint: endpoint,
-          apiTimeout: 1000,
-          maxRetries: 1,
-          dataFlushInterval: 200,
-        },
-        logger,
-      );
 
-      await OpenFeature.setProviderAndWait(clientName, p);
-      const client = OpenFeature.getClient(clientName);
-      await websocketMockServer.connected;
-      await new Promise((resolve) => setTimeout(resolve, 5));
-      client.getBooleanDetails('bool_flag', false);
-      client.getBooleanDetails('bool_flag', false);
-      await new Promise((resolve) => setTimeout(resolve, 250));
-      client.getBooleanDetails('bool_flag', false);
-      client.getBooleanDetails('bool_flag', false);
-      await new Promise((resolve) => setTimeout(resolve, 300));
-
-      expect(fetchMock.calls(dataCollectorEndpoint).length).toBe(2);
-      await OpenFeature.close();
-    });
-
-    it('should not call the data collector before the dataFlushInterval', async () => {
-      const clientName = expect.getState().currentTestName ?? 'test-provider';
-      await OpenFeature.setContext(defaultContext);
-      const p = new GoFeatureFlagWebProvider(
-        {
-          endpoint: endpoint,
-          apiTimeout: 1000,
-          maxRetries: 1,
-          dataFlushInterval: 200,
-        },
-        logger,
-      );
-
-      await OpenFeature.setProviderAndWait(clientName, p);
-      const client = OpenFeature.getClient(clientName);
-      await websocketMockServer.connected;
-      await new Promise((resolve) => setTimeout(resolve, 5));
-      client.getBooleanDetails('bool_flag', false);
-      client.getBooleanDetails('bool_flag', false);
-      await new Promise((resolve) => setTimeout(resolve, 100));
-
-      expect(fetchMock.calls(dataCollectorEndpoint).length).toBe(0);
-      await OpenFeature.close();
-    });
-
-    it('should have a log when data collector is not available', async () => {
-      const clientName = expect.getState().currentTestName ?? 'test-provider';
-      fetchMock.post(dataCollectorEndpoint, 500, { overwriteRoutes: true });
-      await OpenFeature.setContext(defaultContext);
-      const p = new GoFeatureFlagWebProvider(
-        {
-          endpoint: endpoint,
-          apiTimeout: 1000,
-          maxRetries: 1,
-          dataFlushInterval: 200,
-        },
-        logger,
-      );
-
-      await OpenFeature.setProviderAndWait(clientName, p);
-      const client = OpenFeature.getClient(clientName);
-      await websocketMockServer.connected;
-      await new Promise((resolve) => setTimeout(resolve, 5));
-      client.getBooleanDetails('bool_flag', false);
-      client.getBooleanDetails('bool_flag', false);
-      await new Promise((resolve) => setTimeout(resolve, 250));
-
-      fetchMock.post(dataCollectorEndpoint, 500, { overwriteRoutes: true });
+      it('should call the data collector when waiting more than the dataFlushInterval', async () => {
+        const clientName = expect.getState().currentTestName ?? 'test-provider';
+        await OpenFeature.setContext(defaultContext);
+        const p = new GoFeatureFlagWebProvider(
+          {
+            endpoint: endpoint,
+            apiTimeout: 1000,
+            maxRetries: 1,
+            dataFlushInterval: 200,
+          },
+          logger,
+        );
+
+        await OpenFeature.setProviderAndWait(clientName, p);
+        const client = OpenFeature.getClient(clientName);
+        await websocketMockServer.connected;
+        await new Promise((resolve) => setTimeout(resolve, 5));
+
+        client.getBooleanDetails('bool_flag', false);
+        client.getBooleanDetails('bool_flag', false);
+
+        await new Promise((resolve) => setTimeout(resolve, 300));
+
+        expect(fetchMock.calls(dataCollectorEndpoint).length).toBe(1);
+        expect(fetchMock.lastOptions(dataCollectorEndpoint)?.headers).toEqual({
+          'Content-Type': 'application/json',
+          Accept: 'application/json',
+        });
+        await OpenFeature.close();
+      });
+      it('should call the data collector multiple time while waiting dataFlushInterval time', async () => {
+        const clientName = expect.getState().currentTestName ?? 'test-provider';
+        await OpenFeature.setContext(defaultContext);
+        const p = new GoFeatureFlagWebProvider(
+          {
+            endpoint: endpoint,
+            apiTimeout: 1000,
+            maxRetries: 1,
+            dataFlushInterval: 200,
+          },
+          logger,
+        );
+
+        await OpenFeature.setProviderAndWait(clientName, p);
+        const client = OpenFeature.getClient(clientName);
+        await websocketMockServer.connected;
+        await new Promise((resolve) => setTimeout(resolve, 5));
+        client.getBooleanDetails('bool_flag', false);
+        client.getBooleanDetails('bool_flag', false);
+        await new Promise((resolve) => setTimeout(resolve, 250));
+        client.getBooleanDetails('bool_flag', false);
+        client.getBooleanDetails('bool_flag', false);
+        await new Promise((resolve) => setTimeout(resolve, 300));
+
+        expect(fetchMock.calls(dataCollectorEndpoint).length).toBe(2);
+        await OpenFeature.close();
+      });
 
-      client.getBooleanDetails('bool_flag', false);
-      client.getBooleanDetails('bool_flag', false);
-      fetchMock.post(dataCollectorEndpoint, 200, { overwriteRoutes: true });
-      await new Promise((resolve) => setTimeout(resolve, 250));
+      it('should not call the data collector before the dataFlushInterval', async () => {
+        const clientName = expect.getState().currentTestName ?? 'test-provider';
+        await OpenFeature.setContext(defaultContext);
+        const p = new GoFeatureFlagWebProvider(
+          {
+            endpoint: endpoint,
+            apiTimeout: 1000,
+            maxRetries: 1,
+            dataFlushInterval: 200,
+          },
+          logger,
+        );
+
+        await OpenFeature.setProviderAndWait(clientName, p);
+        const client = OpenFeature.getClient(clientName);
+        await websocketMockServer.connected;
+        await new Promise((resolve) => setTimeout(resolve, 5));
+        client.getBooleanDetails('bool_flag', false);
+        client.getBooleanDetails('bool_flag', false);
+        await new Promise((resolve) => setTimeout(resolve, 100));
+
+        expect(fetchMock.calls(dataCollectorEndpoint).length).toBe(0);
+        await OpenFeature.close();
+      });
 
-      const lastBody = fetchMock.lastOptions(dataCollectorEndpoint)?.body;
-      const parsedBody = JSON.parse(lastBody as never);
-      expect(parsedBody['events'].length).toBe(4);
-      await OpenFeature.close();
+      it('should have a log when data collector is not available', async () => {
+        const clientName = expect.getState().currentTestName ?? 'test-provider';
+        fetchMock.post(dataCollectorEndpoint, 500, { overwriteRoutes: true });
+        await OpenFeature.setContext(defaultContext);
+        const p = new GoFeatureFlagWebProvider(
+          {
+            endpoint: endpoint,
+            apiTimeout: 1000,
+            maxRetries: 1,
+            dataFlushInterval: 200,
+          },
+          logger,
+        );
+
+        await OpenFeature.setProviderAndWait(clientName, p);
+        const client = OpenFeature.getClient(clientName);
+        await websocketMockServer.connected;
+        await new Promise((resolve) => setTimeout(resolve, 5));
+        client.getBooleanDetails('bool_flag', false);
+        client.getBooleanDetails('bool_flag', false);
+        await new Promise((resolve) => setTimeout(resolve, 250));
+
+        fetchMock.post(dataCollectorEndpoint, 500, { overwriteRoutes: true });
+
+        client.getBooleanDetails('bool_flag', false);
+        client.getBooleanDetails('bool_flag', false);
+        fetchMock.post(dataCollectorEndpoint, 200, { overwriteRoutes: true });
+        await new Promise((resolve) => setTimeout(resolve, 250));
+
+        const lastBody = fetchMock.lastOptions(dataCollectorEndpoint)?.body;
+        const parsedBody = JSON.parse(lastBody as never);
+        expect(parsedBody['events'].length).toBe(4);
+        await OpenFeature.close();
+      });
     });
   });
+
   it('should resolve when WebSocket is open', async () => {
     const provider = new GoFeatureFlagWebProvider({ endpoint: 'http://localhost:1031', apiTimeout: 1000 });
     await provider.initialize({ targetingKey: 'user-key' });
@@ -658,7 +706,13 @@ describe('GoFeatureFlagWebProvider', () => {
     expect(fetchMock.calls(dataCollectorEndpoint).length).toBe(1);
     const jsonBody = fetchMock.lastOptions(dataCollectorEndpoint)?.body;
     const body = JSON.parse(jsonBody as never) as DataCollectorRequest<never>;
-    expect(body.meta).toEqual({ browser: 'chrome', version: '1.0.0', score: 123, openfeature: true, provider: 'web' });
+    expect(body.meta).toEqual({
+      browser: 'chrome',
+      version: '1.0.0',
+      score: 123,
+      openfeature: true,
+      provider: 'web',
+    });
   });
 });
 
diff --git a/libs/providers/go-feature-flag-web/src/lib/go-feature-flag-web-provider.ts b/libs/providers/go-feature-flag-web/src/lib/go-feature-flag-web-provider.ts
index 4e282d018..d6e9e08d1 100644
--- a/libs/providers/go-feature-flag-web/src/lib/go-feature-flag-web-provider.ts
+++ b/libs/providers/go-feature-flag-web/src/lib/go-feature-flag-web-provider.ts
@@ -10,6 +10,7 @@ import {
   ProviderEvents,
   ResolutionDetails,
   StandardResolutionReasons,
+  TrackingEventDetails,
   TypeMismatchError,
 } from '@openfeature/web-sdk';
 import {
@@ -18,10 +19,12 @@ import {
   GOFeatureFlagAllFlagsResponse,
   GoFeatureFlagWebProviderOptions,
   GOFeatureFlagWebsocketResponse,
+  TrackingEvent,
 } from './model';
 import { transformContext } from './context-transformer';
 import { FetchError } from './errors/fetch-error';
 import { GoFeatureFlagDataCollectorHook } from './data-collector-hook';
+import { CollectorManager } from './collector-manager';
 
 export class GoFeatureFlagWebProvider implements Provider {
   metadata = {
@@ -49,6 +52,8 @@ export class GoFeatureFlagWebProvider implements Provider {
   private _websocket?: WebSocket;
   // _flags is the in memory representation of all the flags.
   private _flags: { [key: string]: ResolutionDetails<FlagValue> } = {};
+
+  private readonly _collectorManager: CollectorManager;
   private readonly _dataCollectorHook: GoFeatureFlagDataCollectorHook;
   // disableDataCollection set to true if you don't want to collect the usage of flags retrieved in the cache.
   private readonly _disableDataCollection: boolean;
@@ -62,13 +67,15 @@ export class GoFeatureFlagWebProvider implements Provider {
     this._maxRetries = options.maxRetries || 10;
     this._apiKey = options.apiKey;
     this._disableDataCollection = options.disableDataCollection || false;
-    this._dataCollectorHook = new GoFeatureFlagDataCollectorHook(options, logger);
+
+    this._collectorManager = new CollectorManager(options, logger);
+    this._dataCollectorHook = new GoFeatureFlagDataCollectorHook(this._collectorManager);
   }
 
   async initialize(context: EvaluationContext): Promise<void> {
     if (!this._disableDataCollection && this._dataCollectorHook) {
       this.hooks = [this._dataCollectorHook];
-      this._dataCollectorHook.init();
+      this._collectorManager.init();
     }
     return Promise.all([this.fetchAll(context), this.connectWebsocket()])
       .then(() => {
@@ -157,8 +164,8 @@ export class GoFeatureFlagWebProvider implements Provider {
   }
 
   async onClose(): Promise<void> {
-    if (!this._disableDataCollection && this._dataCollectorHook) {
-      await this._dataCollectorHook?.close();
+    if (!this._disableDataCollection && this._collectorManager) {
+      await this._collectorManager?.close();
     }
     this._websocket?.close(1000, 'Closing GO Feature Flag provider');
     return Promise.resolve();
@@ -187,6 +194,29 @@ export class GoFeatureFlagWebProvider implements Provider {
     return this.evaluate(flagKey, 'boolean');
   }
 
+  /**
+   * Track allows to send tracking events to a tracking exporter.
+   *
+   * Warning: Note that you need to have a relay proxy with version 1.45.0 or upper to use this feature.
+   * If you are using a version lower than 1.45.0, the events may look weird in your exporter.
+   *
+   * @param trackingEventName
+   * @param context
+   * @param trackingEventDetails
+   */
+  track(trackingEventName: string, context: EvaluationContext, trackingEventDetails: TrackingEventDetails): void {
+    const trackingEvent: TrackingEvent = {
+      kind: 'tracking',
+      contextKind: context['anonymous'] ? 'anonymousUser' : 'user',
+      creationDate: Math.round(Date.now() / 1000),
+      key: trackingEventName,
+      evaluationContext: context,
+      trackingEventDetails: trackingEventDetails,
+      userKey: context.targetingKey || 'undefined-targetingKey',
+    };
+    this._collectorManager?.add(trackingEvent);
+  }
+
   /**
    * extract flag names from the websocket answer
    */
diff --git a/libs/providers/go-feature-flag-web/src/lib/model.ts b/libs/providers/go-feature-flag-web/src/lib/model.ts
index 8982c2375..8fa9ba20d 100644
--- a/libs/providers/go-feature-flag-web/src/lib/model.ts
+++ b/libs/providers/go-feature-flag-web/src/lib/model.ts
@@ -1,5 +1,11 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import { ErrorCode, EvaluationContextValue, FlagValue } from '@openfeature/web-sdk';
+import {
+  ErrorCode,
+  EvaluationContext,
+  EvaluationContextValue,
+  FlagValue,
+  TrackingEventDetails,
+} from '@openfeature/web-sdk';
 
 /**
  * GoFeatureFlagEvaluationContext is the representation of a user for GO Feature Flag
@@ -106,7 +112,7 @@ export interface GOFeatureFlagWebsocketResponse {
 }
 
 export interface DataCollectorRequest<T> {
-  events: FeatureEvent<T>[];
+  events: Array<FeatureEvent<T> | TrackingEvent>;
   meta: Record<string, ExporterMetadataValue>;
 }
 
@@ -122,3 +128,13 @@ export interface FeatureEvent<T> {
   version?: string;
   source?: string;
 }
+
+export interface TrackingEvent {
+  kind: string;
+  contextKind: string;
+  userKey: string;
+  creationDate: number;
+  key: string;
+  evaluationContext: EvaluationContext;
+  trackingEventDetails: TrackingEventDetails;
+}