Run Webtrends Optimize experiences from your own server. You send us a request, we tell you which variation each visitor should see, and you apply the change in your code. No tag on the page is required.
There are two endpoints:
What you want | Endpoint |
One specific experience |
|
All your experiences at once |
|
Both take a JSON body and return JSON. Send the header Content-Type: application/json.
Before you start
You need three values, all available in the Webtrends Optimize platform (or from your Account Manager):
Domain ID: the number that identifies your site, e.g.
108933.keyToken: your API key. Keep it server-side; never expose it in a browser.
Experience ID: only needed for the single-experience endpoint. It is also returned as
params.r_runIDin every response.
Making a request
All request body fields:
Field | Required? | What it does |
| Yes | Authenticates the request. |
| No | What kind of experiences to evaluate; see below. |
| Only when | The page the visitor is on. Used to decide which experiences run there. |
| No | Alternative to |
| No |
|
| No | Cookies from a previous response. Send these to keep a visitor in the same variation; see Keeping visitors sticky. |
| No | Your own key/values for audience targeting, e.g. |
| No | Set to |
The env field
Single-experience endpoint: defaults to
"web", which only returns the experience if it is set to run on theurlyou send. Send"server"to skip the location check entirely: the experience is returned no matter which pages it is set to run on, so this also returns web experiences. With"server", nourlis needed.All-experiences endpoint: defaults to
"all"(every experience, no location check). Send"web"to get only experiences that run on theurlyou send, or"server"to get only server-side experiences (ones with no page location).
Example: one server-side experience
POST https://pat.webtrends-optimize.com/ots/api/rest-1.3/control/108933-32 {
"keyToken": "your-keytoken",
"env": "server"
}Example: one experience on a specific page
POST https://pat.webtrends-optimize.com/ots/api/rest-1.3/control/108933-32 {
"keyToken": "your-keytoken",
"url": "https://www.yoursite.com/pricing"
}Example: every experience that runs on a page
POST https://pat.webtrends-optimize.com/ots/api/rest-1.3/control/108933 {
"keyToken": "your-keytoken",
"env": "web",
"url": "https://www.yoursite.com/pricing"
}Example: a staging experience, for QA
POST https://pat.webtrends-optimize.com/ots/api/rest-1.3/control/108933-32 {
"keyToken": "your-keytoken",
"env": "server",
"s_mode": "staging"
}Reading the response
The single-experience endpoint returns one decision. The all-experiences endpoint returns:
{
"tests": [ ...one decision per experience... ],
"collect": { ...ready-made conversion payload, see Tracking... }
}A decision where the visitor is in the experience looks like this (trimmed):
{
"opcode": "process",
"opstatus": "success",
"body": {
"cookies": {
"_wt.mode-108933": { "timeout": 300000, "type": "session", "value": "WT3cSm..." },
"_wt.user-108933": { "timeout": 7776000000, "type": "persisted", "value": "WT3OH4..." },
"_wt.project-108933-ta_sserv": "1||24-34-34992||true||IN||undefined||…" },
"factors": [
{ "name": "...", "operation": 3, "value": "console.log('variation 1');" }
]
},
"params": {
"testAlias": "ta_sserv",
"r_testID": 24,
"r_runID": 34,
"r_experimentID": 34992,
"r_runState": "TEST"
}
}The parts that matter:
opcode: "process": the visitor is in this experience. Apply the variation.body.factors[].value: the variation content. For code-based experiences it is the script/content you saved against the variation; for a redirect/split it is the destination. Use it however your server renders the experience.params: which experience and variation this is.testAliasis the project alias,r_experimentIDthe variation,r_runIDthe experience,r_testIDthe project.body.cookies: store these and send them back next time (next section).
A decision where the visitor is not in the experience has "opcode": "invalid" and a message telling you why. These are normal; just don't show the variation:
You'll see | Meaning |
| The visitor wasn't selected for the experience (traffic throttle). |
| The visitor didn't match the experience's audience. |
| The experience isn't running in the mode you asked for (e.g. it's staging and you asked for normal). |
| Nothing matched at all; no experiences for this URL or mode. |
Keeping visitors sticky
Without cookies, every request is a brand-new visitor and they may get a different variation each time. To keep a visitor in the same variation:
Take everything in
body.cookiesfrom the response and store it against the visitor (in your session store, or as first-party cookies).On the next request, send each one back inside
cookies, as an object with avalue:
{
"keyToken": "your-keytoken",
"env": "server",
"cookies": {
"_wt.user-108933": { "value": "WT3OH4..." },
"_wt.project-108933-ta_sserv": { "value": "1||24-34-34992||true||IN||undefined||…" }
}
}Two things to note: where the response gave you a cookie as an object (_wt.user, _wt.mode), send back its value string; where it gave you a plain string (_wt.project-...), that string is the value. The _wt.user cookie identifies the visitor and the _wt.project cookie pins their variation, so send both.
Tracking
Page views are counted automatically, once per API request, for the experiences the visitor is actually in. Send "_wt.track": "false" if a request shouldn't count (health checks, retries, QA).
Conversions are sent by you, when the visitor does the thing you're measuring. Every successful response includes a ready-made collect payload (testId and experimentId hold the experience and variation ids):
"collect": {
"domainId": "108933",
"testId": [2434],
"experimentId": [34992],
"visitorId": "1786752272184000256",
"sessionId": "1786752278776",
"user agent": "...",
"dateTime": "2026-08-15 09:30:00",
"timeZone": 3600000,
"customData": {},
"eventName": "conversion"
}
To record a conversion:
Take the
collectobject from the decision response.Change
eventNameto your conversion point's name, e.g."purchase".POST it to
https://collect.webtrends-optimize.com/producerwith headerContent-Type: text/plain.
POST https://collect.webtrends-optimize.com/producer
Content-Type: text/plain
{ "domainId": "108933", "testId": [2434], "experimentId": [34992], "visitorId": "...", "sessionId": "...", "eventName": "purchase", ... }
Store the collect object alongside the visitor's cookies so it's available when the conversion happens later in their session.
Errors
Errors return a JSON body with a machine-readable error code and a human-readable message:
{ "error": "invalid_key_token", "message": "keyToken doesn't match the account" }HTTP status |
| When |
400 |
| The request body isn't valid JSON. |
400 |
| A required field is missing, e.g. no |
400 |
| A field is malformed, e.g. an unknown |
401 |
| The |
404 |
| No experience exists with the ID in the URL. |
410 |
| The Domain ID doesn't exist or the account has been closed. |
500 |
| Something went wrong on our side. Retry, and contact us if it persists. |
Moving from the old API
If you're currently calling https://ots.wtopt.io/ (or wto-ots.webtrends-optimize.workers.dev), that continues to work during the transition. The endpoints above talk to our platform directly, respond faster, and return the conversion payload ready-made. The old track action is replaced by sending conversions to Collect yourself, as described under Tracking.
