> ## Documentation Index
> Fetch the complete documentation index at: https://docs.growthbook.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Event Forwarder

> Forward GrowthBook Cloud tracking events to your warehouse so you can analyze experiments without instrumenting a tracker first.

GrowthBook offers a fully managed event ingestion pipeline. Use our SDKs to track events in your app and we will enrich and forward them to your data warehouse in near real-time.

## Benefits

* **Fully Managed**: No need to worry about infrastructure management, scaling, or maintenance.
* **Seamless Integration**: Built-in tracking in our SDKs.
* **Near Real-Time**: Events are available in your warehouse typically within a few seconds.
* **Broad Support**: Works with BigQuery, Snowflake, and Databricks today, with more warehouses coming soon.

## How it Works

1. Provide GrowthBook with your data warehouse connection details (see [Connection Permissions](#connection-permissions) for required permissions)
2. We will create destination tables for `events`, `experiment_viewed`, and `feature_usage`
3. You send analytics events to our scalable Ingestion API
4. We enrich the events and stream them into your warehouse within seconds
5. We will build a Fact Table, customize the Data Source's experiment assignment queries, and feature usage queries, so you can quickly start analyzing your experiment data and defining metrics.

## Connection Permissions

When setting up the Event Forwarder, GrowthBook uses your data warehouse connection to pre-create destination tables and validate write access.

### BigQuery

The service account configured in your GrowthBook BigQuery datasource must have the **BigQuery Data Editor** role on the destination dataset. This grants the necessary permissions to:

* Check whether destination tables exist
* Create the destination tables (`events`, `experiment_viewed`, `feature_usage`)
* Write data via the BigQuery Storage Write API

You can grant this at the dataset level in the BigQuery console or via the `roles/bigquery.dataEditor` IAM role binding.

### Snowflake

The Snowflake connection **must use key-pair authentication** — password authentication is not supported for the Event Forwarder. GrowthBook creates the three destination tables itself during setup, then streams events into them through [Snowpipe Streaming](https://docs.snowflake.com/en/user-guide/snowpipe-streaming/data-load-snowpipe-streaming-overview) as the connection's user.

Snowpipe Streaming runs under the user's **default role**, so the role you enter in the Event Forwarder must be that user's default role (`ALTER USER <user> SET DEFAULT_ROLE = <role>`). Setup creates the tables and checks them as that same role, so it needs the following privileges:

`CREATE TABLE` and `CREATE SCHEMA` are required because we use Snowpipe Streaming with schema evolution, which may create or alter tables and schemas as your event schema changes.

Key-pair authentication must be configured on the Snowflake datasource connection itself — it is not set in the Event Forwarder UI. Update your datasource's connection settings to use key-pair auth before enabling the Event Forwarder.

### Databricks

#### Before you start: is Zerobus available for your workspace?

The Databricks Event Forwarder writes through [Zerobus Ingest](https://www.databricks.com/product/data-engineering/lakeflow-connect/zerobus-ingest), which is not available everywhere yet. Check three things:

* **Region.** Open the workspace switcher in the top navigation bar to see your workspace region, then confirm it has a check in the Zerobus Ingest column of Databricks' availability table for your cloud: [AWS](https://docs.databricks.com/aws/en/resources/feature-region-support#ingestion), [Azure](https://learn.microsoft.com/en-us/azure/databricks/resources/feature-region-support#ingestion), [Google Cloud](https://docs.databricks.com/gcp/en/resources/feature-region-support#ingestion).
* **Unity Catalog.** Zerobus writes only to Unity Catalog managed tables. The catalog on your GrowthBook connection must be a Unity Catalog catalog, not `hive_metastore`.
* **Databricks OAuth (machine-to-machine)** on the connection, as described below.

Setup creates the tables through your SQL warehouse and does not test Zerobus itself. If your region is unsupported, setup succeeds and the forwarder shows an error after the first event arrives.

The Databricks connection **must use Databricks OAuth (machine-to-machine) authentication** with a Databricks-issued client ID and secret. Personal access tokens keep working for queries, but the Event Forwarder writes through [Zerobus Ingest](https://www.databricks.com/product/data-engineering/lakeflow-connect/zerobus-ingest), which only accepts Databricks OAuth tokens. Switch the datasource connection to Databricks OAuth before enabling the Event Forwarder.

Grant the service principal the following on the destination catalog and schema. GrowthBook creates the three Delta tables itself, so it also gets `SELECT` and `MODIFY` on them as their owner.

```sql theme={null}
GRANT USE CATALOG ON CATALOG <catalog> TO `<service-principal-application-id>`;
GRANT USE SCHEMA, CREATE TABLE ON SCHEMA <catalog>.<schema> TO `<service-principal-application-id>`;
```

In the Event Forwarder UI you provide:

* **Catalog**, pre-filled from your connection when it names one.
* **Schema**, an existing schema the service principal can create tables in. We suggest a dedicated one such as `growthbook`.
* **Table prefix** (optional, default `gb`). GrowthBook creates `<prefix>_events`, `<prefix>_experiment_viewed`, and `<prefix>_feature_usage` in that schema, clustered on `received_at`, with `attributes` and `properties` stored as `VARIANT`.
* **Zerobus endpoint** for your workspace. See [Finding your Zerobus endpoint](#finding-your-zerobus-endpoint) below.

#### Finding your Zerobus endpoint

The endpoint is not shown in the Databricks UI; you build it from two values:

| Cloud | Endpoint |
| - | - |
| AWS | `https://<workspace-id>.zerobus.<region>.cloud.databricks.com` |
| Azure | `https://<workspace-id>.zerobus.<region>.azuredatabricks.net` |
| Google Cloud | `https://<workspace-id>.zerobus.<region>.gcp.databricks.com` |

* **Workspace ID** is the long number in your workspace URL. On AWS and Google Cloud it follows `?o=` (for example `https://dbc-a1b2c3d4-e5f6.cloud.databricks.com/?o=1234567890123456`). On Azure it follows `adb-` in the hostname (`https://adb-1234567890123456.7.azuredatabricks.net`). GrowthBook pre-fills it where the connection host carries it.
* **Region** is the cloud region your workspace runs in, for example `us-east-1`, `eastus`, or `us-central1`. It is shown in the Databricks account console under **Workspaces**.

Zerobus Ingest is a serverless Databricks feature billed to **your** Databricks account per GB ingested. No SQL warehouse or cluster runs on your side to receive events; the SQL warehouse configured on the datasource is only used to create the tables and to run your experiment queries.

## Network Access

For [Event Forwarder](https://docs.growthbook.io/app/event-forwarder.md), you must always allow incoming connections from GrowthBook Cloud's primary ip address (`52.70.79.40/32`).

If you are using our `eu-west-1` ingestor region, you also must allow traffic from `54.74.233.97/32`.

## Usage Limits

Event Forwarder is included on every GrowthBook Cloud plan.

* **Free plans** can forward up to 1,000,000 events per calendar month (UTC). You'll see a notification in GrowthBook when you reach 80% of the limit. Once the limit is exceeded, events stop being forwarded for the remainder of the month and forwarding resumes automatically on the 1st of the next month. Events that arrive while forwarding is stopped are accepted but not delivered.
* **Pro and Enterprise plans** have no cap. Forwarded events are billed per event; see your plan for rates.

Usage is counted once per event. Month-to-date totals and daily history are on **Settings > Usage**, and the Event Forwarder card on your Data Source shows the current month's progress.

## Get Started

Event Forwarder is available to every GrowthBook Cloud organization. Open **Settings > Data Sources**, add or edit a BigQuery, Snowflake or Databricks Data Source, and set up the Event Forwarder from its **Event Forwarder** card. Free plans are subject to the [usage limits](#usage-limits) above; one Event Forwarder is allowed per organization.

If you have questions about higher volumes or Enterprise options, reach out to [sales@growthbook.io](mailto:sales@growthbook.io).

## Sending Events

There are 2 ways to send events to GrowthBook:

1. With our [SDKs](#with-sdks)
2. With our [Ingestion API](#ingestion-api)

### With SDKs

These SDKs have a built-in plugin that automatically sends feature usage and experiment view events to GrowthBook, with the SDK's attributes as context. Don't include sensitive information in your attributes (or anonymize it first). For other SDKs, use the Ingestion API below.

**HTML Script Tag**

Add `data-tracking="growthbook"` to the script tag. It reports events that already fired; it does not enroll users in an experiment.

```html theme={null} theme={null}
<script async
  data-client-key="YOUR_CLIENT_KEY"
  data-tracking="growthbook"
  data-event-ingestor-host="https://us-east-1.gb-ingest.com"
  src="https://cdn.jsdelivr.net/npm/@growthbook/growthbook/dist/bundles/auto.min.js"
></script>
```

Push custom events to the `window.gbEvents` array:

```html theme={null} theme={null}
<script>
  window.gbEvents = window.gbEvents || [];

  // Without properties
  window.gbEvents.push("Page View");

  // With properties
  window.gbEvents.push({
    eventName: "Button Click",
    properties: { button: "Sign Up" },
  });
</script>
```

| Attribute | Description |
| - | - |
| `data-event-ingestor-host` | Ingestor for your Data Region. |
| `data-event-transport` | `auto` (default), `beacon`, or `fetch`, like the JavaScript SDK's `transport` option. |

**JavaScript / React**

Requires `@growthbook/growthbook` v1.4.0 or later. We recommend also adding `autoAttributesPlugin` to include common attributes (browser, `session_id`, UTM parameters, etc.) with every event.

```js theme={null} theme={null}
import { GrowthBook } from "@growthbook/growthbook";
import {
  autoAttributesPlugin,
  growthbookTrackingPlugin,
} from "@growthbook/growthbook/plugins";

const gb = new GrowthBook({
  clientKey: "YOUR_CLIENT_KEY",
  plugins: [
    autoAttributesPlugin(),
    growthbookTrackingPlugin({
      ingestorHost: "https://us-east-1.gb-ingest.com",
    }),
  ],
});
```

Log custom events with `logEvent`. In React, get the instance with `useGrowthBook()`.

```js theme={null} theme={null}
// Without properties
gb.logEvent("Page View");

// With properties
gb.logEvent("Button Click", { button: "Sign Up" });
```

| Option | Description |
| - | - |
| `ingestorHost` | Ingestor for your Data Region. |
| `enableFeatureUsageEvents` | Set to `false` to stop sending feature usage events. Default `true`. v1.8.0+. |
| `transport` | `auto` (default), `beacon`, or `fetch`. See below. v1.7.0+. |

Events are batched and sent with `fetch` using `keepalive`. When the page is hidden or unloading, the queue is flushed with `navigator.sendBeacon`. Both survive the page being torn down, so exposures aren't lost when a user navigates away immediately, which matters most for redirect experiments. Set `transport: "beacon"` to always prefer `sendBeacon`, or `"fetch"` to never use it.

**Node.js**

Requires `@growthbook/growthbook` v1.7.0 or later.

```js theme={null} theme={null}
import { GrowthBookClient } from "@growthbook/growthbook";
import { growthbookTrackingPlugin } from "@growthbook/growthbook/plugins";

const gb = new GrowthBookClient({
  clientKey: "YOUR_CLIENT_KEY",
  plugins: [
    growthbookTrackingPlugin({
      ingestorHost: "https://us-east-1.gb-ingest.com",
    }),
  ],
});
```

Log custom events with `logEvent`, passing the user context:

```js theme={null} theme={null}
gb.logEvent("Sign Up", { accountPlan: "pro" }, userContext);
```

User-scoped instances (for example `req.growthbook` from the [Express middleware](https://docs.growthbook.io/lib/node.md#express-middleware)) don't need the user context:

```js theme={null} theme={null}
req.growthbook.logEvent("Sign Up", { accountPlan: "pro" });
```

Set `enableFeatureUsageEvents: false` (v1.8.0+) to stop sending feature usage events.

**Python**

Requires `growthbook` v2.2.0 or later, plus the `requests` package:

```bash theme={null} theme={null}
pip install growthbook requests
```

```python theme={null} theme={null}
from growthbook import GrowthBook, GrowthBookTrackingPlugin

gb = GrowthBook(
    client_key="YOUR_CLIENT_KEY",
    attributes={"id": "user_123"},
    plugins=[
        GrowthBookTrackingPlugin(
            ingestor_host="https://us-east-1.gb-ingest.com",
        )
    ],
)
gb.load_features()
```

Log custom events with `log_event`:

```python theme={null} theme={null}
gb.log_event("Button Click", {"button": "Sign Up"})
```

Call `gb.destroy()` when you're done with the instance to flush pending events.

| Option | Description |
| - | - |
| `ingestor_host` | Ingestor for your Data Region. |
| `track_feature_evaluated` | Set to `False` to stop sending feature usage events. Default `True`. |
| `batch_size` | Events to queue before sending. Default `10`. |
| `batch_timeout` | Seconds to wait before sending a partial batch. Default `10.0`. |

> [!NOTE]
>
> Use the `GrowthBook` class. The async `GrowthBookClient` does not send events to GrowthBook yet.

**PHP**

Requires `growthbook/growthbook` v2.4.0 or later.

```php theme={null} theme={null}
use Growthbook\Growthbook;
use Growthbook\GrowthBookTrackingPlugin;
use Growthbook\GrowthBookTrackingPluginConfig;

$growthbook = Growthbook::create()
  ->withAttributes(['id' => $userId])
  ->addPlugin(new GrowthBookTrackingPlugin(
    new GrowthBookTrackingPluginConfig(
      ingestorHost: "https://us-east-1.gb-ingest.com",
    )
  ));

$growthbook->initialize("YOUR_CLIENT_KEY");
```

Call `addPlugin()` after any `with*()` methods. Those return a copy of the instance, and a discarded copy closes the plugin. The plugin gets its client key from `initialize()`, and pending events are sent when the request ends.

Custom events aren't supported yet. Send them with the [Ingestion API](https://docs.growthbook.io/app/managed-warehouse.md#ingestion-api).

| Option | Description |
| - | - |
| `ingestorHost` | Ingestor for your Data Region. |
| `batchSize` | Events to queue before sending mid-request. Default `100`. |

**Go**

Requires `github.com/growthbook/growthbook-golang` v0.5.0 or later.

```go theme={null} theme={null}
import (
    "context"

    gb "github.com/growthbook/growthbook-golang"
)

client, err := gb.NewClient(
    context.Background(),
    gb.WithClientKey("YOUR_CLIENT_KEY"),
    gb.WithSseDataSource(),
    gb.WithGrowthBookTracking(gb.TrackingPluginConfig{
        IngestorHost: "https://us-east-1.gb-ingest.com",
    }),
)
if err != nil {
    // handle error
}
defer client.Close() // sends pending events
```

Log custom events with `LogEvent`, on a client scoped to the current user. Don't call `Close()` on that client; it closes the shared plugin.

```go theme={null} theme={null}
userClient, _ := client.WithAttributes(gb.Attributes{"id": "user-123"})

userClient.LogEvent(context.Background(), "Button Click", gb.EventProperties{"button": "Sign Up"})
```

| Option | Description |
| - | - |
| `IngestorHost` | Ingestor for your Data Region. |
| `BatchSize` | Events to queue before sending. Default `100`. |
| `BatchTimeout` | How long to wait before sending a partial batch. Default `10s`. |

**Java**

Requires the GrowthBook Java SDK v0.11.1 or later.

```java theme={null} theme={null}
import growthbook.sdk.java.multiusermode.GrowthBookClient;
import growthbook.sdk.java.multiusermode.configurations.Options;
import growthbook.sdk.java.multiusermode.configurations.UserContext;
import growthbook.sdk.java.plugin.tracking.GrowthBookTrackingPlugin;
import growthbook.sdk.java.plugin.tracking.TrackingPluginConfig;
import java.util.Collections;

String clientKey = "YOUR_CLIENT_KEY";

GrowthBookTrackingPlugin trackingPlugin = GrowthBookTrackingPlugin.of(
    TrackingPluginConfig.builder()
        .clientKey(clientKey)
        .ingestorHost("https://us-east-1.gb-ingest.com")
        .build()
);

GrowthBookClient client = new GrowthBookClient(
    Options.builder()
        .apiHost("https://cdn.growthbook.io")
        .clientKey(clientKey)
        .plugins(Collections.singletonList(trackingPlugin))
        .build()
);

if (!client.initialize()) {
    client.shutdown();
    throw new IllegalStateException("GrowthBook initialization failed");
}

UserContext user = UserContext.builder()
    .attributesJson("{\"id\":\"user-123\"}")
    .build();

boolean enabled = client.isOn("my-feature", user);
```

The plugin automatically sends feature usage and experiment view events, with the user's attributes as context. The plugin doesn't inherit the client's SDK connection key, so pass it to both `TrackingPluginConfig` and `Options`.

Keep one client for your application and create a separate tracking plugin for each client. At application shutdown, call `client.shutdown()` to flush pending events and release resources:

```java theme={null} theme={null}
client.shutdown();
```

Custom events aren't supported yet. Send them with the [Ingestion API](https://docs.growthbook.io/app/managed-warehouse.md#ingestion-api).

| Option | Description |
| - | - |
| `ingestorHost` | Ingestor for your Data Region. |
| `batchSize` | Events to queue before sending. Default `100`. |
| `batchTimeout` | How long to wait before sending a partial batch. Default `Duration.ofSeconds(10)`. |
| `closeTimeout` | Time budget for flushing events during shutdown. Default `Duration.ofSeconds(5)`. |

**Kotlin**

Requires the GrowthBook Kotlin SDK v7.8.0 or later, with `NetworkDispatcherKtor` v1.2.0+ or `NetworkDispatcherOkHttp` v1.1.0+.

```kotlin theme={null} theme={null}
import com.sdk.growthbook.GBSDKBuilder
import com.sdk.growthbook.network.GBNetworkDispatcherKtor
import com.sdk.growthbook.plugin.TrackingPluginConfig
import com.sdk.growthbook.plugin.tracking.GrowthBookTrackingPlugin

val networkDispatcher = GBNetworkDispatcherKtor()

val growthBook = GBSDKBuilder(
    apiKey = "YOUR_CLIENT_KEY",
    apiHost = "https://cdn.growthbook.io/",
    attributes = attributes,
    networkDispatcher = networkDispatcher,
    trackingCallback = { _, _ -> },
)
    .setPlugins(
        listOf(
            GrowthBookTrackingPlugin(
                TrackingPluginConfig(
                    clientKey = "YOUR_CLIENT_KEY",
                    networkDispatcher = networkDispatcher,
                    ingestorHost = "https://us-east-1.gb-ingest.com",
                )
            )
        )
    )
    .initialize()
```

The plugin doesn't inherit the builder's client key or network dispatcher, so pass both to `TrackingPluginConfig`. Without a network dispatcher, no events are sent.

Custom events aren't supported yet. Send them with the [Ingestion API](https://docs.growthbook.io/app/managed-warehouse.md#ingestion-api).

| Option | Description |
| - | - |
| `ingestorHost` | Ingestor for your Data Region. |
| `batchSize` | Events to queue before sending. Default `100`. |
| `batchTimeout` | How long to wait before sending a partial batch. Default `10.seconds`. |

**Swift**

Requires the GrowthBook Swift SDK v1.1.15 or later.

```swift theme={null} theme={null}
import GrowthBook

let gb: GrowthBookSDK = GrowthBookBuilder(
    apiHost: "https://cdn.growthbook.io",
    clientKey: "YOUR_CLIENT_KEY",
    attributes: ["id": "user-123"],
    trackingCallback: { _, _ in }
)
.addPlugin(GrowthBookTrackingPlugin(
    config: .init(ingestorHost: "https://us-east-1.gb-ingest.com")
))
.initializer()
```

Custom events aren't supported yet. Send them with the [Ingestion API](https://docs.growthbook.io/app/managed-warehouse.md#ingestion-api).

| Option | Description |
| - | - |
| `ingestorHost` | Ingestor for your Data Region. |
| `batchSize` | Events to queue before sending. Default `100`. |
| `batchTimeout` | Seconds to wait before sending a partial batch. Default `10`. |

#### Sending events to the right region

The tracking plugin sends events to `https://us-east-1.gb-ingest.com` by default. If you selected **eu-west-1** as your Event Forwarder's Data Region, set the ingestor host option shown for your SDK above to `https://eu-west-1.gb-ingest.com`. Otherwise events won't reach the proper ingestion server and your events will be dropped.

You can find your Event Forwarder's configured region on the data source's Event Forwarder settings.

### Ingestion API

You can also send events directly to our ingestion API. Pass an array of event objects, each with the following properties:

* **event\_name**: The name of the event (e.g., "Purchase", "Button Click")
* **properties**: Optional key-value pairs with properties of the event itself
* **attributes**: Optional key-value pairs with attributes of the user or context at the time of the event
* YOUR\_WAREHOUSE\_REGION: either us-east-1 or eu-west-1 depending on what you selected when creating your datasource
* YOUR\_CLIENT\_KEY: The key from your sdk connection. Go to [https://app.growthbook.io/sdks/](https://app.growthbook.io/sdks/) click on your connection or make a new one. Copy the text from the "Client Key"

```bash theme={null}
curl -X POST "https://YOUR_WAREHOUSE_REGION.gb-ingest.com/track?client_key=YOUR_CLIENT_KEY" \
-H "Content-Type: application/json" \
-d '[{
  "event_name": "Purchase",
  "properties": {
    "amount": 100
  },
  "attributes": {
    "user_id": "12345"
  }
}]'
```

Using the wrong host means events won't reach the proper ingestion server and your events will be dropped.

#### Experiment View Events

In order to use GrowthBook's experiment analysis features, you must send an event every time a user views an experiment. It must match the following format:

* **event\_name**: Must be `"Experiment Viewed"`
* **properties**: Must include the following key/value pairs:
  * `experimentId`: The ID of the experiment being viewed
  * `variationId`: The ID of the variation that was shown to the user

In addition, you must include `attributes` with the user attributes that were used to evaluate the experiment, plus any attributes you want to use as dimensions for slicing and dicing.

#### Feature Usage Events

To take advantage of GrowthBook's feature usage analytics, you must send an event every time a feature is evaluated with a specific format.

* **event\_name**: Must be `"Feature Evaluated"`
* **properties**: Must include the following key/value pairs:
  * `feature`: The name of the feature being evaluated
  * `value`: The feature's value that was returned from the evaluation
  * `source`: (optional) The source of the feature value (e.g., "defaultValue", "experiment", "force")
  * `ruleId`: (optional) The ID of the specific rule that was used to evaluate the feature (or `$default` if the default value was used)
  * `variationId`: (optional) If the value came from an experiment, the ID of the variation that was returned

#### Attributes

Attributes are key-value pairs that provide context about the user or environment at the time of the event. They can be used to slice and dice your data in analysis.

It's recommended to include the same attributes you use in your GrowthBook SDK.

Some attributes are enriched in the ingestion API if provided:

* `ip` - a geoip lookup is done and the following attributes are added. If an ip attribute is not provided, we will use the IP address of the request.
  * `geo_country`
  * `geo_city`
  * `geo_lat`
  * `geo_lon`
* `ua` - the user agent is parsed and the following attributes are added. If a user agent attribute is not provided, we will use the user agent of the request.
  * `ua_browser` (e.g. Safari)
  * `ua_os` (e.g. macOS)
  * `ua_device_type` (e.g. mobile)
* `url` - the URL is parsed and the following attributes are added:
  * `url_path` (e.g. /products/123)
  * `url_host` (e.g. [www.example.com](http://www.example.com))
  * `url_query` (e.g. ?utm\_source=google)
  * `url_fragment` (e.g. #section1)

It's also recommend to include attributes about which SDK is being used. This can help with debugging.

* `sdk_language` (e.g. python)
* `sdk_version` (e.g. 1.2.3)

#### Limits

When calling the ingestion API directly, please be aware of the following default limits:

* Maximum of 100 events per request
* Maximum of 1 request per second

These limits are to protect accidental misuse of the API. Our Ingestion API can handle much higher volumes than this. Reach out to your account manager if you need to increase your limits.
