Direct Tracking Endpoint

In a Web Experimentation project the tracking script normally sends every bucketing and conversion event for you. If you need to send events yourself — from your own server, from an edge worker, from a checkout callback, or from page JavaScript running somewhere the tracking script is not — you can post them directly to the Convert tracking endpoint.

This page covers the request shape for a Web Experimentation project and a User-Agent requirement that applies only when you call the endpoint from a server.

Prefer to try it interactively? The Send Tracking page in the API Reference has a Try It panel that submits a real request to the live endpoint. It needs no credentials.

Running a FullStack project instead? Its tracking endpoint takes an SDK key in place of the account and project IDs, and it is authenticated. See Direct Tracking Endpoint in the FullStack documentation.

Endpoint shape

Send a POST request to:

https://metrics.convertexperiments.com/v1/track/<accountId>/<projectId>

Your Convert account ID and project ID go in the path. The endpoint takes no authentication — no API key, no bearer token, no auth header of any kind. The only header you need is Content-Type: application/json (plus a User-Agent, if you are calling from a server — see below).

Because the IDs are in the path, you do not need to repeat them in the body.

The endpoint reflects the request Origin and allows POST, so calling it from browser JavaScript works without a proxy.

Request body

The body follows the same JSON shape the tracking script uses internally:

FieldTypeDescription
visitorsarrayOne entry per visitor (see below)
accountIdstringOptional. Ignored on this route — the path value is used
projectIdstringOptional. Ignored on this route — the path value is used

Each entry inside visitors carries:

FieldTypeDescription
visitorIdstringThe unique visitor identifier you are tracking
eventsarrayOne entry per event for that visitor (bucketing or conversion)
segmentsobjectOptional segment data (browser, device, country, source, campaign, custom)

Per-event shape:

eventTypedataNotes
bucketing{ experienceId, variationId }Records that a visitor was bucketed into a variation
conversion{ goalId, goalData?, bucketingData? }Records a goal completion

Refer to the API Reference for the full schema, optional fields, and response codes.

Server-side callers only: identify your traffic

This section does not apply to browser JavaScript. Skip it unless you are calling the endpoint from a server, a worker, or a script.

You need it if both of the following are true:

  1. You are building the tracking request yourself instead of letting the tracking script build it for you.
  2. Your HTTP client uses its default User-Agent (for example node, undici, axios/X, node-fetch/X, GuzzleHttp/X, python-requests/X, Java/X, okhttp/X, Apache-HttpClient/X, curl/X).

Convert's tracking server applies bot detection to every incoming request before any tracking work happens. Default User-Agent strings used by server-side HTTP clients look like bot traffic to that filter and are silently dropped: the server returns 200 OK and your client believes the event was accepted, but the event never reaches your reports or Live Logs.

To opt out of the bot check, set the User-Agent request header to a value that starts with ConvertAgent/<version>. The exact version is up to you and is only used to identify your integration in logs.

ConvertAgent/1.0

You can embed ConvertAgent/<version> inside a longer User-Agent if you also want to keep your own client identifier:

MyApp/2.5 ConvertAgent/1.0 (production)

The trailing slash in ConvertAgent/ is required — bare ConvertAgent without a version token does not qualify. This prevents accidental matches against unrelated clients that happen to include the word in their User-Agent.

Browser JavaScript never needs any of this. A real browser's User-Agent passes the bot check on its own, and browsers do not let page code set the User-Agent header anyway.

Examples

A minimal bucketing event, with no authentication in any of them:

await fetch(
  `https://metrics.convertexperiments.com/v1/track/${accountId}/${projectId}`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      visitors: [
        {
          visitorId,
          events: [
            {
              eventType: 'bucketing',
              data: {
                experienceId: String(experienceId),
                variationId: String(variationId)
              }
            }
          ]
        }
      ]
    })
  }
);
await fetch(
  `https://metrics.convertexperiments.com/v1/track/${accountId}/${projectId}`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'User-Agent': 'ConvertAgent/1.0'
    },
    body: JSON.stringify({
      visitors: [
        {
          visitorId,
          events: [
            {
              eventType: 'bucketing',
              data: {
                experienceId: String(experienceId),
                variationId: String(variationId)
              }
            }
          ]
        }
      ]
    })
  }
);
$body = json_encode([
    'visitors' => [
        [
            'visitorId' => $visitorId,
            'events' => [
                [
                    'eventType' => 'bucketing',
                    'data' => [
                        'experienceId' => (string) $experienceId,
                        'variationId' => (string) $variationId,
                    ],
                ],
            ],
        ],
    ],
]);

$url = "https://metrics.convertexperiments.com/v1/track/{$accountId}/{$projectId}";

$request = $requestFactory
    ->createRequest('POST', $url)
    ->withHeader('Content-Type', 'application/json')
    ->withHeader('User-Agent', 'ConvertAgent/1.0')
    ->withBody($streamFactory->createStream($body));

$httpClient->sendRequest($request);
curl -X POST "https://metrics.convertexperiments.com/v1/track/${ACCOUNT_ID}/${PROJECT_ID}" \
  -H "Content-Type: application/json" \
  -H "User-Agent: ConvertAgent/1.0" \
  -d '{
    "visitors": [{
      "visitorId": "'"${VISITOR_ID}"'",
      "events": [{
        "eventType": "bucketing",
        "data": { "experienceId": "100123", "variationId": "100456" }
      }]
    }]
  }'

Verifying your integration

After your first event lands, open the project's Live Logs in the Convert dashboard. If you see your bucketing or conversion entries appear within a few seconds, the request is being accepted and attributed correctly. If Live Logs stay empty:

  • If you are calling from a server, confirm the User-Agent header on every outbound request contains ConvertAgent/<version>. A single missing header is enough for the bot filter to silently drop the request while still answering 200.
  • Confirm the account ID and project ID in the path, and visitorId, experienceId and variationId in the body, exactly match values that exist in your project. Events referencing experience IDs that are not in the project's active experiences are rejected downstream.
  • See Debugging & Preview Mode for broader diagnostic steps.

When to use the tracking script instead

The direct endpoint is the lowest-friction path when the tracking script cannot run where your event happens — a server-side conversion, a webhook, a headless storefront's backend. For everything a page can see, the tracking script handles bucketing, batching, deduplication, revenue goals and the User-Agent convention for you. See What the Tracking Script Does and Goal Types.


Did this page help you?