Personyze is usually installed as a browser tracker: the snippet decides what to show and renders it in the page. But the same decision engine is reachable directly over HTTP, which lets your own server ask "what should this user see?" and render the answer itself.
Use this when:
- You render HTML on the server and don't want a client-side flash of default content.
- You're personalizing something that isn't a web page at all — a native app, a kiosk, an email pipeline, a set-top box.
- You want to A/B test a server-side change (a different API response, a different price, a different ranking) rather than a visual swap.
- You're on a platform with no Personyze SDK. For Android and iOS, prefer the mobile SDKs, which implement everything on this page for you.
The endpoint
One endpoint does everything: you send what the user just did, and you get back what should happen next.
POST https://app.personyze.com/rest/tracker-v1
Authenticated like the rest of the API — HTTP Basic, username api, password your API key. Content-Type must be application/json.
The request
{
"user_id": 123456,
"session_id": "…value from the previous response…",
"languages": "en, es",
"time_zone": -4,
"screen": "1920x1080",
"os": "Ubuntu/18.04",
"platform": "x86_64",
"device_type": "regular",
"commands": [
["Navigate", "urn:personyze:doc:checkout"]
]
}
| Field | Notes |
|---|---|
user_id |
Any 32-bit integer that is stable for this user. Your own user id, a device id, a hash — whatever you can reproduce next time. |
session_id |
null on the very first request. After that, always send back the session_id from the most recent response. |
commands |
What happened, and what you want back. See below. |
languages, time_zone, screen, os, platform, device_type |
Describe the environment. All optional, but they feed targeting rules and appear in Real time visitors, so send what you have. device_type is one of regular, tablet, phone. |
new_session |
Set true to force a fresh session instead of failing when a stored session_id has gone stale. |
The session_id is the whole state of the conversation. Store it wherever you keep per-user state, send back the newest one every time, and Personyze knows who it is talking to. If you send a malformed one you get Invalid session_id — unless new_session is set, in which case a new session starts.
The response
{
"session_id": "…store this…",
"cache_version": 17,
"conditions": [{"id": 42}],
"actions": [{"id": 88, "data": {"data": "…"}}],
"dismiss_conditions": [],
"dismiss_actions": [51]
}
| Field | Meaning |
|---|---|
conditions |
The campaigns that currently match this user. |
actions |
What to show. Each entry is an action id; data carries the per-user content when the action has any. |
dismiss_actions, dismiss_conditions |
Ids that no longer apply and should be taken down if you are still showing them. |
session_id |
The updated session. Store it. |
cache_version |
Changes when the account's configuration changes — useful if you cache action content on your side. |
Your server decides what an action means. The id is the contract: action 88 might be "show the free-shipping banner", or "use ranking algorithm B". You map ids to behaviour in your own code, or read the content out of data.
Asking for a decision
Personyze will not execute the same action twice on the same "page". So every time you want a fresh decision, send a Navigate command with a document identifier:
["Navigate", "urn:personyze:doc:checkout"]
The name is any URL-encoded text and shows up in the dashboard. Without a Navigate, you'll log events but get no new actions back.
Reporting what you did
This is the step that makes A/B results real, and it's the one most easily forgotten. After you act on a decision, tell Personyze:
["action status", 88, "executed"]
| Status | Send it when |
|---|---|
executed |
You actually rendered or applied the action. |
target |
The user engaged with it — clicked, converted, did the thing you were testing for. |
close |
The user dismissed it. |
An action that is never reported executed looks like it never ran, and its variant will appear to have no traffic.
When a user interacts with a product that a Personyze action recommended, attribute it:
["Product Viewed", "SKU-8891", "action_id", 88]
Other commands
The same command list drives profile updates and catalog interactions:
["User Profile", "first_name", "John", "industry", "e-commerce"]— write profile fields.["Product Viewed", "SKU-1"],["Product Added to cart", "SKU-1"],["Product Liked", "SKU-1"],["Product Purchased", "SKU-1"],["Product Removed from cart", "SKU-1"],["Product Unliked", "SKU-1"],["Products Purchased"],["Products Unliked"].["Article Viewed", "ID"],["Article Liked", "ID"],["Article Commented", "ID"],["Article Goal", "ID"],["Article Unliked", "ID"],["Articles Unliked"].
Commands are executed in order, so you can log an event and ask for a decision in a single round trip.
Running an A/B test
- Create the test in the panel. Personyze has a dedicated A/B Testing campaign type, and any campaign can turn on A/B testing: you get two 50/50 groups plus winner criteria. Personyze assigns each user to a group and keeps them there — you do not implement the split yourself, and you must not, or the two systems will disagree about who is in which arm.
- Give each arm an action. Variant A is one action id, variant B another. For a purely server-side test the action needs no visual content at all — the id alone tells your code which branch to take.
- Call
tracker-v1with aNavigateat the point of decision, and look at which action id comes back. - Branch on the id, and report
["action status", <id>, "executed"]. - Report the outcome in the form your winner criterion actually measures — see the table below. This is the step that decides whether the test can be scored at all.
- Read the verdict in the panel. See "Where the results live" below for what the API can and cannot give you here.
Because Personyze holds the assignment, a user who arrives through your server one day and through the browser tracker the next stays in the same arm, provided both identify them the same way.
What each winner criterion measures
A test is scored on the criteria you pick when configuring it. There are four, and each is fed by a different signal — reporting the wrong one means the arm looks like it produced nothing:
| Criterion | Scored from | So you must send |
|---|---|---|
| Clicks | Sessions in which the action was engaged with | ["action status", <id>, "target"] |
| Products purchased | Sessions containing an attributed purchase | A purchase interaction carrying action_id — ["Product Purchased", "SKU-1", "action_id", 88] |
| Goal event | Sessions in which that configured goal event fired | Whatever fires that goal event in your account |
| Bounce rate | Sessions with only one page view — lower is better | Nothing extra; it follows from Navigate |
["action status", <id>, "target"] drives the Clicks criterion and only that one. If your test is scored on purchases, target will not move it — send the attributed product interaction instead. In every case, also send executed, or the arm has no denominator.
Where the results live
The A/B verdict itself is computed by Personyze and read in the panel. Significance, improvement over control and the winner are calculated per A/B group, against a control group, and that comparison is not exposed over REST.
summary_actions gives you the daily numbers per action — n_executed, n_delivered, n_sessions_extra, goal counts and value. If each arm of your test is its own action, that is a perfectly good way to track how each variant is doing in your own reporting. What you cannot reconstruct from it is lift against the control group: the control arm shows no action, so it has no per-action rows to read.
So: watch the arms over the API, take the verdict from the panel.
Practical notes
- Keep the session. Losing
session_idstarts a new session and can re-bucket the user. Persist it next to your own session state, not in memory. - One call per decision point. Batch events into the same call that asks for the decision instead of chattering.
- Fail open. If
tracker-v1is slow or errors, render your default. Never let a personalization call block a checkout. Set a short timeout and treat "no actions" and "call failed" as the same path. - Errors follow the usual status codes.
A complete worked example
The custom recommendations app guide walks through this same endpoint end to end, in PHP, for the specific case of pulling product recommendations into a non-web application.