---
title: Log Custom Exposure Events
description: "Manually logs exposure events for experiments or feature gates. Useful for analytics-only experiments, delayed exposure logging, or when automatic exposure l…"
product: general
token_estimate: 2524
---
# Log Custom Exposure Events

> For AI agents: a documentation index is available at [/llms.txt](/llms.txt). Append `.md` to any page URL for markdown, or send `Accept: text/markdown`.

## Log Custom Exposure Events

**POST** `/v1/log_custom_exposure`

Full URL: `https://api.statsig.com/v1/log_custom_exposure`

**Servers:**
- SDK API Server: `https://api.statsig.com/v1/log_custom_exposure`
- Events API Server: `https://events.statsigapi.net/v1/log_custom_exposure`

Log Custom Exposure Events

Manually logs exposure events for experiments or feature gates. Useful for analytics-only experiments, delayed exposure logging, or when automatic exposure logging is disabled.

## Authorizations

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| statsig-api-key | string | Yes | — | SDK API key (Server Secret or Client SDK Key) |

## Body (application/json)

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| exposures | object[] | Yes | — | Array of exposure events to log |
| exposures.experimentName | string | No | — | Name of the experiment or dynamic config Constraints: min length: 1 |
| exposures.user | object | No | — | User object containing identification and attributes for evaluation. At minimum, provide at least one identifier. |
| exposures.user.userID | string | No | user-123 | Primary user identifier |
| exposures.user.email | string | No | user@example.com | User email address Constraints: format: email |
| exposures.user.ip | string | No | 192.168.1.1 | User IP address for geo-targeting |
| exposures.user.userAgent | string | No | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 | User agent string for device/browser targeting |
| exposures.user.country | string | No | US | 2-letter country code (ISO 3166-1 alpha-2) |
| exposures.user.locale | string | No | en_US | Locale/language code |
| exposures.user.appVersion | string | No | 1.2.3 | Application version |
| exposures.user.custom | object | No | {"subscription_plan":"premium","account_age_days":45,"is_beta_tester":true} | Custom user attributes for targeting (string, number, boolean, or array of strings) |
| exposures.user.privateAttributes | object | No | {"internal_user_id":"12345"} | Private attributes used for evaluation but not logged to analytics |
| exposures.user.customIDs | object | No | {"companyID":"company-456","deviceID":"device-789"} | Additional custom identifier mappings |
| exposures.user.statsigEnvironment | object | No | — | Environment tier for targeting |
| exposures.user.statsigEnvironment.tier | string | No | — | Environment tier Allowed values: production, staging, development |
| exposures.group | string | No | — | Group name (use this OR ruleID) Constraints: min length: 1 |
| exposures.ruleID | string | No | — | Rule ID (use this OR group) |
| exposures.time | oneOf | No | — | Exposure timestamp Constraints: integer (format: int64), string |
| exposures.secondaryExposures | object[] | No | — | — |
| exposures.secondaryExposures.gate | string | Yes | — | Name of the gate |
| exposures.secondaryExposures.gateValue | string | Yes | — | Gate value as string ('true' or 'false') |
| exposures.secondaryExposures.ruleID | string | Yes | — | Rule ID that was evaluated |
| exposures.gateName | string | No | — | Name of the feature gate Constraints: min length: 1 |
| exposures.passes | boolean | No | — | Whether the gate passed |
| user | object | No | — | Shared user object for all exposures |
| user.userID | string | No | user-123 | Primary user identifier |
| user.email | string | No | user@example.com | User email address Constraints: format: email |
| user.ip | string | No | 192.168.1.1 | User IP address for geo-targeting |
| user.userAgent | string | No | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 | User agent string for device/browser targeting |
| user.country | string | No | US | 2-letter country code (ISO 3166-1 alpha-2) |
| user.locale | string | No | en_US | Locale/language code |
| user.appVersion | string | No | 1.2.3 | Application version |
| user.custom | object | No | {"subscription_plan":"premium","account_age_days":45,"is_beta_tester":true} | Custom user attributes for targeting (string, number, boolean, or array of strings) |
| user.privateAttributes | object | No | {"internal_user_id":"12345"} | Private attributes used for evaluation but not logged to analytics |
| user.customIDs | object | No | {"companyID":"company-456","deviceID":"device-789"} | Additional custom identifier mappings |
| user.statsigEnvironment | object | No | — | Environment tier for targeting |
| user.statsigEnvironment.tier | string | No | — | Environment tier Allowed values: production, staging, development |
| statsigMetadata | object | No | {"sdkType":"js-client","sdkVersion":"4.20.0","exposureLoggingDisabled":false} | SDK metadata for tracking SDK type, version, and other diagnostic information |
| statsigMetadata.sdkType | string | No | — | SDK type sending the request (e.g., js-client) |
| statsigMetadata.sdkVersion | string | No | — | SDK version |
| statsigMetadata.exposureLoggingDisabled | boolean | No | — | When true, prevents the HTTP API from automatically logging exposures. Use this only if you will handle exposure logging yourself. |

## Response (application/json)

**202** — Exposures accepted for processing

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| success | boolean | Yes | — | Whether the request was successful |

```json
{
  "success": true
}
```

## Code samples

### cURL

```bash
curl -X POST "https://api.statsig.com/v1/log_custom_exposure" \
  -H "Content-Type: application/json" \
  -H "statsig-api-key: YOUR_API_KEY" \
  -d '{
  "exposures": [
    {
      "user": {
        "userID": "user-123"
      },
      "experimentName": "checkout_flow_v2",
      "group": "Test Group",
      "ruleID": "rule_abc123",
      "secondaryExposures": [
        {
          "gate": "is_employee",
          "gateValue": "false",
          "ruleID": "default"
        }
      ]
    }
  ]
}'
```

### Python

```python
import requests

response = requests.post(
    "https://api.statsig.com/v1/log_custom_exposure",
    headers={
        "Content-Type": "application/json",
        "statsig-api-key": "YOUR_API_KEY"
    },
    json={
  "exposures": [
    {
      "user": {
        "userID": "user-123"
      },
      "experimentName": "checkout_flow_v2",
      "group": "Test Group",
      "ruleID": "rule_abc123",
      "secondaryExposures": [
        {
          "gate": "is_employee",
          "gateValue": "false",
          "ruleID": "default"
        }
      ]
    }
  ]
}
)
data = response.json()
```

### JavaScript

```javascript
const response = await fetch("https://api.statsig.com/v1/log_custom_exposure", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "statsig-api-key": "YOUR_API_KEY"
  },
  body: JSON.stringify({
  "exposures": [
    {
      "user": {
        "userID": "user-123"
      },
      "experimentName": "checkout_flow_v2",
      "group": "Test Group",
      "ruleID": "rule_abc123",
      "secondaryExposures": [
        {
          "gate": "is_employee",
          "gateValue": "false",
          "ruleID": "default"
        }
      ]
    }
  ]
})
});
const data = await response.json();
```

### PHP

```php
<?php
$ch = curl_init("https://api.statsig.com/v1/log_custom_exposure");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Content-Type: application/json",
    "statsig-api-key: YOUR_API_KEY"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$body = json_encode({
  "exposures": [
    {
      "user": {
        "userID": "user-123"
      },
      "experimentName": "checkout_flow_v2",
      "group": "Test Group",
      "ruleID": "rule_abc123",
      "secondaryExposures": [
        {
          "gate": "is_employee",
          "gateValue": "false",
          "ruleID": "default"
        }
      ]
    }
  ]
});
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
$response = curl_exec($ch);
curl_close($ch);
```

### Go

```go
package main

import (
  "bytes"
  "net/http"
)

func main() {
body := []byte("{\"exposures\":[{\"user\":{\"userID\":\"user-123\"},\"experimentName\":\"checkout_flow_v2\",\"group\":\"Test Group\",\"ruleID\":\"rule_abc123\",\"secondaryExposures\":[{\"gate\":\"is_employee\",\"gateValue\":\"false\",\"ruleID\":\"default\"}]}]}")
req, _ := http.NewRequest("POST", "https://api.statsig.com/v1/log_custom_exposure", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("statsig-api-key", "YOUR_API_KEY")
  client := &http.Client{}
  resp, _ := client.Do(req)
  defer resp.Body.Close()
}
```

### Java

```java
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://api.statsig.com/v1/log_custom_exposure"))
      .method("POST", HttpRequest.BodyPublishers.ofString("{\"exposures\":[{\"user\":{\"userID\":\"user-123\"},\"experimentName\":\"checkout_flow_v2\",\"group\":\"Test Group\",\"ruleID\":\"rule_abc123\",\"secondaryExposures\":[{\"gate\":\"is_employee\",\"gateValue\":\"false\",\"ruleID\":\"default\"}]}]}"))
.header("Content-Type", "application/json")
      .header("statsig-api-key", "YOUR_API_KEY")
      .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
```

### Ruby

```ruby
require "net/http"
require "json"

uri = URI("https://api.statsig.com/v1/log_custom_exposure")
request = Net::HTTP::Post.new(uri)
request["Content-Type"] = "application/json"
request["statsig-api-key"] = "YOUR_API_KEY"
request.body = "{\"exposures\":[{\"user\":{\"userID\":\"user-123\"},\"experimentName\":\"checkout_flow_v2\",\"group\":\"Test Group\",\"ruleID\":\"rule_abc123\",\"secondaryExposures\":[{\"gate\":\"is_employee\",\"gateValue\":\"false\",\"ruleID\":\"default\"}]}]}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
  http.request(request)
end
```

