SplitLab/ Developer Docs
Contact

API reference

Conversions API

Report conversions to SplitLab from your server, without adding a tracking snippet to your own pages.

Overview

SplitLab runs A/B tests on your landing pages. A conversion is counted for the variant the visitor saw. When the conversion happens on your side (a thank-you page, a booking, a sale, an audit completed in your own tool), your server can report it to SplitLab with a single HTTP call instead of loading our script.

You need two things: an API key from SplitLab, and the three visitor values SplitLab hands to your site.

How it works

  1. A visitor lands on a SplitLab page and is shown one variant of the test.
  2. When they move on to your site or your tool, SplitLab passes three values along with them: sl_tid, sl_vid and sl_vh.
  3. Your site keeps those values with the lead.
  4. When the conversion happens, your server sends them to the Conversions API.
  5. SplitLab counts the conversion for the variant that visitor saw.
ValueMeaning
sl_tidThe A/B test
sl_vidThe variant the visitor saw
sl_vhThe visitor

Set up

There are no goals to create. Name each conversion with event_name (for example Lead or Purchase). The first time a test receives a name, SplitLab creates a goal for it on that test, and every later conversion with that name is counted under it, the way Facebook's Conversions API works. See Events and goals.

1. Get an API key. In SplitLab, open the client and go to API Keys → New API key. The key is shown once; keep it on your server. See Authentication.

2. If your tool opens inside the page (iframe), put the placeholders in the URL the iframe actually loads. That is the iframe's src, or its data-src when the page's script copies data-src into src (common for popups and lazy-loaded embeds). See Embedded tools (iframes). Links, buttons and forms need nothing.

3. Do it on every variant. Each variant has its own page, so the placeholders must be in each variant's HTML, or visitors on the variants without them can't be counted.

4. Test with the live test link. Open the test's shareable link in a private window, wait a couple of seconds, then go through your flow. Values are not filled in the page editor.

Getting the values

When a visitor leaves the SplitLab page for your domain, through a link, a button or a form, SplitLab adds the values to the URL automatically. Nothing to set up:

https://yoursite.com/thank-you?sl_tid=…&sl_vid=…&sl_vh=…
  • Forms that post to your server: the values are on that POST's URL as well. Read them there, because a redirect to your thank-you page may drop them.
  • Forms sent with JavaScript (AJAX): the values are added to the form's action URL, so send the request to the form's action rather than a URL written into your script.
  • Thank-you page on the same domain as the SplitLab page: the values are only added when the visitor moves to a different domain. Use the email / phone fallback.

If your tool opens inside the SplitLab page instead, see Embedded tools (iframes).

Embedded tools (iframes)

A booking widget, quiz, calculator or audit tool that opens inside the SplitLab page (an iframe) is never navigated to, so SplitLab cannot add the values to its URL automatically. And because sl_vh is different for every visitor, the values cannot be typed into the page either.

Instead, write placeholders into the iframe's URL. Every time SplitLab serves the page, it replaces them with that visitor's values:

HTML on the SplitLab page
<iframe src="https://yourtool.com/start?sl_tid={{sl_tid}}&sl_vid={{sl_vid}}&sl_vh={{sl_vh}}"></iframe>
What the visitor's browser loads
<iframe src="https://yourtool.com/start?sl_tid=4b8f0c1e-…&sl_vid=9a2d7e55-…&sl_vh=c3f1a9d2-…"></iframe>

Inside your tool

  1. Read sl_tid, sl_vid and sl_vh from the URL your tool was loaded with.
  2. Keep them with the visitor's session or lead.
  3. When the visitor converts, send them from your server to the Conversions API.
Reading the values in your tool (JavaScript)
const params = new URLSearchParams(window.location.search);
const splitlab = {
  sl_tid: params.get('sl_tid'),
  sl_vid: params.get('sl_vid'),
  sl_vh: params.get('sl_vh'),
};
// Send these to your server with the lead; your server calls the Conversions API.

Put the placeholders in the attribute the iframe really loads from. If the iframe has a data-src that the page's script copies into src when a popup opens, the placeholders go in data-src.

Popup or lazy-loaded iframe
<iframe data-src="https://yourtool.com/start?sl_tid={{sl_tid}}&sl_vid={{sl_vid}}&sl_vh={{sl_vh}}"></iframe>

Add them on every variant of the test. Each variant is its own page.

Rules

  • Write the placeholders exactly as shown: {{sl_tid}}, {{sl_vid}}, {{sl_vh}}, with no spaces inside the braces.
  • They work anywhere in the page's HTML, including src, data-src and links, and in embeds added to the page in SplitLab.
  • They are filled when SplitLab serves the test page. In the page editor they stay as written, which is expected.
  • Only the iframes you add placeholders to receive the values; other embeds on the page get nothing.

Authentication

Every request carries your API key as a Bearer token. Create one in SplitLab: open the client, go to API Keys → New API key and give it a name. Keys look like sl_live_xxxxxxxx_… and are shown only once, so copy it straight away.

Authorization: Bearer YOUR_API_KEY
  • Keep the key on your server. Requests from a browser are rejected.
  • A key only works for your own tests.
  • Creating and revoking keys needs manager access to the client; anyone with access can see the list and when each key was last used.
  • If a key is lost or exposed, create a new one, switch your server to it, then revoke the old one on the same page. A revoked key stops working at once. You can hold several keys at a time, so no conversions are lost in between.

Request

POST https://www.trysplitlab.com/api/v1/conversions
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "sl_tid": "4b8f0c1e-…",
  "sl_vid": "9a2d7e55-…",
  "sl_vh": "c3f1a9d2-…",
  "event_name": "Lead"
}
FieldRequiredNotes
sl_vid + sl_vhYes, or email / phoneThe values SplitLab passed to your site
sl_tidNoIf sent, must belong to the same test as sl_vid
emailFallbackAny letter case
phoneFallbackAny format; only the digits are compared
event_nameRecommendedWhat happened, for example Lead or Purchase. Up to 100 characters. Left out, API conversion is used. See Events and goals
goal_idNoCount the conversion on an existing goal instead. Takes priority over event_name

Email / phone fallback

If the sl_* values were lost, send the email address or phone number the visitor entered in the form on the SplitLab page. SplitLab looks up their form submission from the last 90 days and counts the conversion for the variant they saw.

{ "email": "[email protected]" }
{ "phone": "+1 (404) 555-0142" }

This only works when the visitor filled in a form on the SplitLab page itself. Use it for the cases where the values cannot reach you, for example when your thank-you page is on the same domain as the SplitLab page (the values are only added when the visitor moves to a different domain).

Events and goals

Send an event_name and SplitLab does the rest. The first conversion with a new name creates a goal of that name on the test; later conversions with the same name are counted under it. Nothing has to be set up in SplitLab first.

{ "sl_vid": "…", "sl_vh": "…", "event_name": "Lead" }
{ "sl_vid": "…", "sl_vh": "…", "event_name": "Purchase" }
  • Each name becomes its own goal, shown in the test's goals with an API event label. Names are matched ignoring letter case, so Lead and lead are the same goal.
  • Without event_name, conversions are counted on a goal called API conversion.
  • A goal is created per test. Sending Lead for two tests gives each test its own Lead goal.
  • The same visitor is counted once per event: a repeat returns duplicate: true. A visitor can still count for Lead and later Purchase.
  • In the results, conversions are counted once per visitor, whichever events they reached. The Goals column shows every event reached.
  • To count a conversion on a goal that already exists instead (for example one set up in SplitLab), send its goal_id, copied with the button next to the goal under Settings → Conversion Goals. A click or form goal is also recorded in the visitor's browser, so if the visitor already clicked, the API call returns duplicate: true.

Responses

Every response is JSON. Errors include an error message saying what was wrong.

StatusMeaning
200Recorded: { ok, conversion_id, test_id, variant_id, goal_id, event_name, matched_by }. matched_by is sl_vid, email or phone
200Already recorded for this visitor and event: { ok: true, duplicate: true, … }. Safe to ignore
400Invalid payload (for example an empty event_name), or sl_tid doesn't match sl_vid
401Missing, wrong or revoked API key
403The test does not belong to your account
404Unknown variant, no visit recorded for this visitor, or no form submission matched the email / phone
422goal_id is not a goal of this test and variant
429More than 60 requests a minute. Retry after the number of seconds in the Retry-After header

Examples

cURL
curl -X POST https://www.trysplitlab.com/api/v1/conversions \
  -H "Authorization: Bearer $SPLITLAB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sl_tid":"…","sl_vid":"…","sl_vh":"…","event_name":"Lead"}'
Node.js
const res = await fetch('https://www.trysplitlab.com/api/v1/conversions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SPLITLAB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ sl_tid, sl_vid, sl_vh, event_name: 'Lead' }),
});
const result = await res.json(); // { ok: true, conversion_id, ... }
Python
import os, requests

res = requests.post(
    "https://www.trysplitlab.com/api/v1/conversions",
    headers={"Authorization": f"Bearer {os.environ['SPLITLAB_API_KEY']}"},
    json={"sl_tid": sl_tid, "sl_vid": sl_vid, "sl_vh": sl_vh, "event_name": "Lead"},
    timeout=10,
)
print(res.status_code, res.json())
PHP
$ch = curl_init('https://www.trysplitlab.com/api/v1/conversions');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'Authorization: Bearer ' . getenv('SPLITLAB_API_KEY'),
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode(['sl_tid' => $slTid, 'sl_vid' => $slVid, 'sl_vh' => $slVh, 'event_name' => 'Lead']),
]);
$result = json_decode(curl_exec($ch), true);

Good to know

  • Retries are safe. The same visitor and event are counted once; a repeat returns duplicate: true.
  • Report late if you need to. The values stay valid, so a conversion that happens days later can still be sent.
  • Where it shows. API conversions appear in the test's results in SplitLab. They don't add a row to the Leads list, because the visitor's form submission is already there.
  • Server only. The API sends no CORS headers, so a call from browser code fails by design.

Troubleshooting

You seeCheck
401The key is sent as "Authorization: Bearer …" and was copied in full
404 No pageviewThe sl_vid and sl_vh are from the same visit and were not edited. Values from a SplitLab preview link are not counted
404 No lead matchedThe email / phone is the one entered on the SplitLab page, within the last 90 days
422The goal_id is not a goal of this test, or is limited to another variant. Leave goal_id out and send event_name instead
200 with duplicate: true on the first callYou sent a goal_id of a click or form goal the visitor already reached in the browser. Send an event_name instead
Two goals for what should be one eventThe names differ by more than letter case (Lead vs Leads). Use one exact name everywhere
A variant never gets API conversionsThat variant's page is missing the placeholders. Add them on every variant
{{sl_vid}} arrives literallyThe iframe URL is on a page not served through a SplitLab test (an editor preview, for example). Check on the live test URL

Still stuck? Contact us with the request body (without the key) and the response.