All / Analytics / Conversions
CallTrackingMetrics Integration: Track Calls and See Variations
If phone calls are how your visitors convert, AB Split Test connects to CallTrackingMetrics (CTM) in two directions. You can see which test variation each caller saw inside your CTM reports, and you can record qualifying calls as conversions back in your AB Split Test results.
This guide covers both. You can set up either one on its own.
How it works
AB Split Test attaches the visitor’s anonymous ID to their CTM session before the CTM tracking script loads. Every call that visitor places carries that ID.
From there, your site exposes an endpoint that turns the ID into test data. Hand it a visitor ID and it returns every test that visitor entered and which variation they saw.
The reason it works this way round is timing. CTM reads visitor variables once, as its tracking script loads. At that moment your visitor often has not been assigned a variation yet — and one visitor can be in several tests at once, or enter a new test on a later page. Sending just the ID and asking afterwards captures all of that, rather than a snapshot of a single page.
What you need
- The CTM tracking code installed on your site
- A paid AB Split Test plan
- Advanced Visitor Tracking enabled in AB Split Test
Step 1: Turn on the integration
Go to AB Split Test → Settings → Tracking and enable Advanced Visitor Tracking. Without it, the plugin does not store the per-visitor records this integration depends on.
Then go to AB Split Test → Settings → Integrations and enable CallTrackingMetrics attribution.
That attaches the visitor ID to CTM as a custom variable named abst_uuid, early enough in the page that CTM picks it up. You do not need to add any custom tracking code in CTM yourself.
Step 2: Get the visitor ID onto the call
Both halves of the integration need the visitor ID available on the call record in CTM.
Create the custom fields
In CTM, go to Settings → Custom Fields. Add these, each with type Text and the dropdown on the right set to Save to Activity.
| Field name | API name | Holds |
|---|---|---|
| AB Visitor ID | abst_uuid | The visitor ID, used for the lookup |
| AB Tests | abst_tests | Every test the caller was in, with variations |
| AB Variation | abst_variation | The variation from their most recent test |
The API name is what your code refers to later. You can set it when creating the field but not change it afterwards, so get these right first time.
Create the capture trigger
- Go to Flows → Triggers and click New Trigger.
- Name it something you will recognise, such as AB Split Test — capture visitor ID.
- In the Trigger dropdown, select When a website session is associated to the call record.
- Choose which numbers it applies to, or enable Trigger for all Activities.
- Click + Add Workflow, then remove the default rule with the trash icon.
- Click Add Action and select Update Field.
- Choose AB Visitor ID, and in the box to the right enter
abst_uuid. - Save changes.
Place a test call from your own site, then open that call in the call log. AB Visitor ID should show an ID like 438bc713-32fd-43ce-b621-aeac1534df5d.
That alone is useful — you can now trace an individual call back to a variation using the Visitor Lookup screen, which takes a visitor ID and shows every test that visitor entered. The next two steps automate it so you do not have to look calls up one by one.
Step 3: See variations in your CTM reports
Your site exposes this endpoint:
GET /wp-json/abst/v1/user-data?uuid={visitor-uuid}
Authenticate with HTTP Basic, using a WordPress username and application password. Create one under Users → Profile → Application Passwords.
Use a dedicated user for this. An application password grants that user’s full permissions across the WordPress REST API, not just this endpoint, so create a user with the Editor role rather than using your administrator account. Editor is enough to read test data, and you can revoke it any time.
curl -u "username:application-password" \
"https://example.com/wp-json/abst/v1/user-data?uuid=438bc713-32fd-43ce-b621-aeac1534df5d"
It returns every test that visitor entered, oldest first. Note that last_activity is the most recent recorded activity for that test, not the moment they entered it — conversions and goals update it:
{
"uuid": "438bc713-32fd-43ce-b621-aeac1534df5d",
"found": true,
"tests": [
{
"test_id": 3814,
"test_name": "Homepage Hero Headline",
"variation": "magic-2",
"converted": false,
"goals": [],
"last_activity": "2026-08-17 20:15:36",
"device_size": "desktop"
}
]
}
| Response | Meaning |
|---|---|
200 with "found": true | The visitor’s test history |
200 with "found": false | Valid request, but no record of this visitor |
| 400 | The uuid is not a valid visitor ID |
| 401 | Credentials missing or wrong |
| 404 | Advanced Visitor Tracking is turned off |
Nothing about this endpoint is CTM-specific. Any system holding an AB Split Test visitor ID can call it — a CRM, an automation platform, or your own scripts.
Writing the result back to CTM
To get the data into your CTM reports, take the visitor ID off the call, call the endpoint, and write the answer back onto the call record. Which route you use depends on your CTM plan.
| Route | CTM plan | Best for |
|---|---|---|
| Zapier or Make | Marketing Pro and above | Most people — no code |
| Your own server | Marketing Pro and above | You already run a backend |
| CTM Lambda | Sales Engage and above | Staying entirely inside CTM |
Route A: Zapier or Make
- In CTM, create a webhook that fires when a call completes, pointing at your Zap or Make scenario.
- Read the AB Visitor ID and the call’s
idfrom the incoming payload. - Add an HTTP GET step to the endpoint above, with Basic authentication.
- Add a second HTTP step that writes the result back onto the call in CTM.
POST https://api.calltrackingmetrics.com/api/v1/accounts/{account_id}/calls/{call_id}/modify.json
{
"call": {
"custom_fields": {
"abst_tests": "Homepage Hero Headline: magic-2",
"abst_variation": "magic-2"
}
}
}
Authenticate that call with your CTM API key and secret as HTTP Basic. Both Zapier and Make have a generic HTTP request action — in Zapier it is Webhooks by Zapier → Custom Request — so you do not need a prebuilt CallTrackingMetrics action.
Route B: Your own server
Point a CTM webhook at your backend and do the same three steps in whatever language you like. The endpoint is a plain authenticated GET returning JSON.
Route C: CTM Lambda
Keeps everything inside CTM. Requires the Sales Engage, Enterprise, or Connect plan.
Go to Flows → Lambdas and create a Lambda. Add three environment variables with + Add Variable so the credentials stay out of the code: WP_URL, WP_USER and WP_APP_PASSWORD.
Then paste in the function below, save, and use the built-in tester against a call you know has a visitor ID.
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
// The visitor ID lands in the AB Visitor ID custom field via the Step 2 trigger.
// The fallback scan covers accounts where it arrives under a different name.
function findVisitorId(call) {
// Custom fields may arrive nested under custom_fields or flattened onto the
// activity itself, depending on account setup - check both, then fall back to
// any field holding something UUID-shaped.
const sources = [call.custom_fields || {}, call];
for (const source of sources) {
const direct = source.abst_uuid;
if (direct && UUID_RE.test(String(direct).trim())) {
return String(direct).trim();
}
}
for (const source of sources) {
for (const value of Object.values(source)) {
if (typeof value === 'string' && UUID_RE.test(value.trim())) {
return value.trim();
}
}
}
return null;
}
exports.handler = async (event, context) => {
const ctm = context.ctm;
const call = event.activity;
// Setting up? Uncomment this and run the tester to see the whole call object.
// console.log(JSON.stringify(call, null, 2));
const visitorId = findVisitorId(call);
if (!visitorId) {
console.log('No AB Split Test visitor ID on this call - skipping.');
return;
}
const base = String(process.env.WP_URL).replace(/\/+$/, '');
const auth = Buffer.from(
`${process.env.WP_USER}:${process.env.WP_APP_PASSWORD}`
).toString('base64');
const response = await fetch(
`${base}/wp-json/abst/v1/user-data?uuid=${encodeURIComponent(visitorId)}`,
{ headers: { Authorization: `Basic ${auth}` } }
);
if (!response.ok) {
console.log(`Lookup failed with status ${response.status}`);
return;
}
const data = await response.json();
if (!data.found || !data.tests || data.tests.length === 0) {
console.log('Visitor has no test history.');
return;
}
// Commas separate tags in CTM, so strip them out of test names.
const clean = (text) => String(text).replace(/,/g, ' ').trim();
// A long-running visitor can be in many tests. Keep the summary inside a
// sensible length for a text field rather than letting CTM truncate it.
let summary = data.tests
.map((test) => `${clean(test.test_name)}: ${test.variation}`)
.join(' | ');
if (summary.length > 250) {
summary = `${summary.slice(0, 247)}...`;
}
// tag_list replaces every existing tag, so merge rather than overwrite.
const existingTags = String(call.tag_list || '')
.split(',')
.map((tag) => tag.trim())
.filter(Boolean);
const testTags = data.tests.map(
(test) => `abtest: ${clean(test.test_name)} - ${test.variation}`
);
const tagList = Array.from(new Set([...existingTags, ...testTags])).join(',');
await ctm.update({
custom_fields: {
abst_tests: summary,
abst_variation: data.tests[data.tests.length - 1].variation
},
tag_list: tagList
});
console.log(`Tagged call with: ${summary}`);
};
To run it automatically, go to Flows → Triggers, create a trigger, click Add Action, select Run Lambda Function and choose your Lambda. Triggering on when a website session is associated to the call record records the variation as the call starts; a call-completed trigger runs later, so the conversion status is more likely to be final.
Step 4: Record calls as conversions in AB Split Test
The other direction: count a qualifying call as a conversion in your test results. Your site has the endpoint built in. It is a GET request, so every parameter goes in the query string and no request body is needed.
https://yoursite.com/wp-admin/admin-ajax.php?action=ab_fp_event&type=conversion&eid=TEST_ID&uuid=VISITOR_UUID
action=ab_fp_eventandtype=conversion— always exactly these valueseid— the test’s ID, shown in the test list in your WordPress adminuuid— the visitor ID, taken from the AB Visitor ID field you set up in Step 2
Repeat calls from the same visitor are de-duplicated automatically, so each visitor converts once per test.
Setting it up in CTM
- Go to Flows → Triggers and create a new trigger.
- Set the conditions that define a real call for you — for example, call ended with talk time over 60 seconds.
- Add a Webhook action with the method set to GET, and build the URL with your own values.
- Insert the visitor ID dynamically using CTM’s token picker, pulling from the AB Visitor ID field.
- Save, then place a test call to verify.
Add one webhook action per test, each with its own eid, if you are tracking calls against more than one test.
Troubleshooting
The AB Visitor ID field stays empty. Check the CallTrackingMetrics setting is enabled in AB Split Test. Then view the source of a page on your site and search for abst-ctm-cvars — you should find a small script near the top of the head. If it is not there, Advanced Visitor Tracking is probably off. If it is there but the field is still empty, confirm the capture trigger is assigned to the tracking number you called, and check the value in the Update Field action — it should be abst_uuid exactly, though some accounts expect the custom field’s display name instead.
Status 401. The username or application password is wrong. Application passwords contain spaces when WordPress displays them; those are optional, but the password is otherwise case-sensitive. Test with the curl command above before assuming the problem is in CTM.
Status 404. Advanced Visitor Tracking is off in AB Split Test settings.
Status 200 but "found": false. The visitor ID is valid but your site has no record of it. Usually the caller never entered a test.
The conversion webhook returns “UUID/Advanced ID not updated”. Either that visitor already converted, which is working as intended, or the visitor ID did not match a recorded visit.
Calls show the wrong variation. Check the test was still running when the call happened, and whether the visitor may have cleared their browser storage between visits, which gives them a new ID and a fresh assignment.
FAQ
Do I have to set up both halves? No. Step 3 puts test data in your CTM reports. Step 4 counts calls as conversions in AB Split Test. They are independent.
Does this work with the free plan? No — integrations require a paid plan.
Can I track calls against more than one test? Yes. The lookup returns every test the visitor entered, and for conversions you add one webhook action per test.
What about caller privacy? Only the anonymous visitor ID travels between your site and CTM. AB Split Test never receives the caller’s phone number or any personal details.
What if visitors have not accepted consent? If you have consent gating enabled, visitors who have not accepted will not have a visitor ID, so their calls will not carry test data.