Skip to main content

Server Side 1.3

D
Written by Dan Bier

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

POST https://pat.webtrends-optimize.com/ots/api/rest-1.3/control/{domainId}-{experienceId}

All your experiences at once

POST https://pat.webtrends-optimize.com/ots/api/rest-1.3/control/{domainId}

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_runID in every response.

Making a request

All request body fields:

Field

Required?

What it does

keyToken

Yes

Authenticates the request.

env

No

What kind of experiences to evaluate; see below.

url

Only when env is "web"

The page the visitor is on. Used to decide which experiences run there.

_wm_referer

No

Alternative to url. If both are sent, url wins.

s_mode

No

"normal" (live experiences, the default) or "staging" (experiences still in QA).

cookies

No

Cookies from a previous response. Send these to keep a visitor in the same variation; see Keeping visitors sticky.

data

No

Your own key/values for audience targeting, e.g. {"loggedIn": "true"}.

_wt.track

No

Set to "false" to stop this request counting a page view.

The env field

  • Single-experience endpoint: defaults to "web", which only returns the experience if it is set to run on the url you 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", no url is needed.

  • All-experiences endpoint: defaults to "all" (every experience, no location check). Send "web" to get only experiences that run on the url you 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. testAlias is the project alias, r_experimentID the variation, r_runID the experience, r_testID the 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

errorcode: 417 "Throttled out"

The visitor wasn't selected for the experience (traffic throttle).

errorcode: 302 "No test matched any segments"

The visitor didn't match the experience's audience.

errorcode: 303 "No test found"

The experience isn't running in the mode you asked for (e.g. it's staging and you asked for normal).

errorCode: 501 "No experiences found on domain"

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:

  1. Take everything in body.cookies from the response and store it against the visitor (in your session store, or as first-party cookies).

  2. On the next request, send each one back inside cookies, as an object with a value:

{
"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:

  1. Take the collect object from the decision response.

  2. Change eventName to your conversion point's name, e.g. "purchase".

  3. POST it to https://collect.webtrends-optimize.com/producer with header Content-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

error

When

400

invalid_json

The request body isn't valid JSON.

400

missing_parameter

A required field is missing, e.g. no keyToken, or no url when env is "web".

400

invalid_parameter

A field is malformed, e.g. an unknown env value, a non-numeric experience ID, or a cookies object in the wrong shape.

401

invalid_key_token

The keyToken doesn't match this Domain ID.

404

test_not_found

No experience exists with the ID in the URL.

410

deleted_account

The Domain ID doesn't exist or the account has been closed.

500

internal_error

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.

Did this answer your question?