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:
| Field | Type | Description |
|---|---|---|
visitors | array | One entry per visitor (see below) |
accountId | string | Optional. Ignored on this route — the path value is used |
projectId | string | Optional. Ignored on this route — the path value is used |
Each entry inside visitors carries:
| Field | Type | Description |
|---|---|---|
visitorId | string | The unique visitor identifier you are tracking |
events | array | One entry per event for that visitor (bucketing or conversion) |
segments | object | Optional segment data (browser, device, country, source, campaign, custom) |
Per-event shape:
eventType | data | Notes |
|---|---|---|
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:
- You are building the tracking request yourself instead of letting the tracking script build it for you.
- 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.0You 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-Agentheader on every outbound request containsConvertAgent/<version>. A single missing header is enough for the bot filter to silently drop the request while still answering200. - Confirm the account ID and project ID in the path, and
visitorId,experienceIdandvariationIdin 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.
Updated 10 days ago