Getting Started

Read and store the click ID

Where the tracking script keeps a visitor's click ID, every way to read it – the window.lj JavaScript API, browser storage, a hidden form field, the clickId cookie on your server and the lj_click_id URL parameter – and how to combine them so your own booking system, form or backend sends a click_id with every event.

When you send events yourself – from a custom booking system, an in-house form or your own backend – the click_id is what ties each event to the exact campaign, ad set and ad. This page shows where the tracking script keeps the click ID, every way to read it, and how to combine those ways so you still have the ID when one of them fails.

In short

  1. In the browser, read the click ID when the form is submitted with window.lj.getClickId(), and fall back to browser storage and the URL.
  2. Add a hidden lj_click_id field to the form as a second path, and read the clickId cookie on your server as a third.
  3. Save the ID on the booking or contact record and send it with every event – together with email and phone.

Where the click ID lives

When a visitor lands with lj_click_id in the URL (every tracked link carries it), the tracking script saves that ID. A visitor without one gets a new click ID from the script. So on any page where the script has run, the visitor has a click ID – and it's stored in several places at once:

WhereNameKept forWho can read it
URL parameterlj_click_idUntil the script has saved it – then it's removed from the address barJavaScript on that page load
localStorageclickIdUntil the visitor clears site dataJavaScript on the same host
CookieclickId60 daysJavaScript on the same host and your server
sessionStorageclickIdUntil the tab is closedJavaScript in the same tab

Storage doesn't cross domains – or subdomains

All of these belong to the exact host the script ran on. www.yourbrand.com and booking.yourbrand.com can't read each other's storage or cookie. Between hosts, the click ID has to travel in the URL as lj_click_id – see Across domains, subdomains and iframes.

Read it when the form is submitted, not on page load

After a page loads, the script reports the visit to LeadJourney. The response can contain a corrected click ID – the script then replaces every stored copy and every filled hidden field. Reading the ID at the moment of submit always gives you the final value.

Ways to read the click ID

Once the script has loaded, it exposes window.lj:

CallWhat it does
await window.lj.getClickId()Returns the click ID. Checks the URL, localStorage, the cookie and sessionStorage, in that order.
window.lj.mountClickId()Writes the click ID into the page's hidden fields again (see hidden form field).
window.lj.getTrackingStatus()Returns the status of the visit – see Check that it works.
const clickId = await window.lj.getClickId();

window.lj only exists after the script has loaded. If an ad blocker or consent tool stops the script, or it isn't installed on the page, window.lj is undefined – which is why the recommended setup falls back to the other ways.

2. Browser storage

You can read the stored values directly. This still works when the script is blocked on the current page view, as long as it ran on an earlier visit to the same host:

localStorage.getItem("clickId");
sessionStorage.getItem("clickId");
decodeURIComponent(document.cookie.match(/(?:^|; )clickId=([^;]*)/)?.[1] ?? "");

3. A hidden form field

Add a hidden input to your form – no JavaScript needed. The script fills it with the click ID automatically:

<input type="hidden" name="lj_click_id" />

The script recognises the names lj_click_id, lj_clickid, click_id and clickid (also with a trailing _, and as form_fields[lj_click_id] for Elementor), and inputs whose aria-label is one of those names. Fields added after page load – a later step of a multi-step form, a widget that renders late – are filled as soon as they appear. If your own code resets the value, call window.lj.mountClickId() to fill it again.

Two limits of the hidden field

  • Only the first matching field on the page is filled. With several forms on one page, set the value yourself when the form is submitted (see step 2 below).
  • The script sets the value directly, without an input event. React, Vue and other controlled forms don't see it in their state – read the ID with getClickId() at submit instead.

The browser sends the clickId cookie with every request to the same host the script runs on. If your booking form submits to that host, your server can read the ID without any JavaScript:

// with the cookie-parser middleware
const clickId = req.cookies.clickId ?? null;
$clickId = $_COOKIE['clickId'] ?? null;
click_id = request.cookies.get("clickId")

The cookie is set for the exact host only. A request from www.yourbrand.com to api.yourbrand.com does not carry it.

5. From the URL

On a page without the tracking script, the click ID arrives as ?lj_click_id=… – for example when a visitor clicks from your website to a booking system on another host. It's only in the URL of that first page, so read it once on page load and keep it:

// Only on pages WITHOUT the tracking script – run once on page load
const fromUrl = new URLSearchParams(window.location.search).get("lj_click_id");
if (fromUrl) {
  try {
    localStorage.setItem("clickId", fromUrl);
  } catch {}
}

Storing it under clickId means the helper below finds it the same way as on pages with the script. On pages with the script, don't rely on the URL: the script removes lj_click_id from the address bar as soon as it has saved the ID.

No single way works everywhere – scripts get blocked, forms live on other subdomains, React forms ignore hidden fields, and bookings often reach your system days before the next event. Layer the ways so that each one covers the gaps of the others.

Read the click ID with fallbacks

Add this helper to the pages with your booking form. It tries the script's API first, then the stored values, then the URL:

// Returns the visitor's LeadJourney click ID, or null if there is none.
async function getClickId() {
  try {
    const id = await window.lj?.getClickId();
    if (id) return id;
  } catch {}

  const fallbacks = [
    () => localStorage.getItem("clickId"),
    () => decodeURIComponent(document.cookie.match(/(?:^|; )clickId=([^;]*)/)?.[1] ?? ""),
    () => sessionStorage.getItem("clickId"),
    () => new URLSearchParams(window.location.search).get("lj_click_id"),
  ];
  for (const read of fallbacks) {
    try {
      const id = read();
      if (id) return id;
    } catch {}
  }
  return null;
}

Send it along when the form is submitted

Call the helper at submit and send the result to your backend. Keep the hidden lj_click_id field in the form as well – if your JavaScript fails, the field still carries the ID.

<form id="booking-form" method="post" action="/bookings">
  <!-- your fields: name, email, phone, date … -->
  <input type="hidden" name="lj_click_id" />
</form>

<script>
  const form = document.getElementById("booking-form");
  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    const field = form.elements.namedItem("lj_click_id");
    if (!field.value) field.value = (await getClickId()) ?? "";
    form.submit();
  });
</script>
bookingForm.addEventListener("submit", async (event) => {
  event.preventDefault();
  const data = Object.fromEntries(new FormData(bookingForm));

  await fetch("/api/bookings", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ ...data, click_id: await getClickId() }),
  });
});

On your server, take the first value you find – the one sent by JavaScript, then the hidden field, then the cookie:

const clickId =
  req.body.click_id || req.body.lj_click_id || req.cookies.clickId || null;
$clickId = null;
foreach ([$_POST['click_id'] ?? null, $_POST['lj_click_id'] ?? null, $_COOKIE['clickId'] ?? null] as $value) {
    if (!empty($value)) {
        $clickId = $value;
        break;
    }
}
click_id = (
    request.form.get("click_id")
    or request.form.get("lj_click_id")
    or request.cookies.get("clickId")
)

Save it on the booking and the contact

Store the click ID on the record the moment the booking is created – for example in a column called lj_click_id on the booking and on the contact. Later milestones (the call took place, the deal was won) happen in your system, often days later and without a browser. They read the stored ID back instead of looking for it again.

Send it with every event – plus email and phone

From your server, send each event to the Conversion API with the stored click ID and the contact's email and phone:

{
  "type": "BookedCall",
  "click_id": "8f3c2a1e-4b7d-4c1a-9e2f-6d5b3a7c9e10",
  "email": "[email protected]",
  "phone": "+491701234567",
  "first_name": "Anna",
  "last_name": "Schmidt"
}

Never hold back an event because the click ID is missing

If no click ID could be found, send the event anyway. LeadJourney also matches on email and phone, so the booking is still captured and can be attributed.

Across domains, subdomains and iframes

Because storage belongs to one host, the click ID has to travel in the URL whenever your booking system runs somewhere else than the page the visitor landed on.

The booking form is on the same host as the rest of your site (e.g. yourbrand.com/booking). Nothing extra to do – the helper, the hidden field and the cookie all work.

The booking system runs on a subdomain (e.g. booking.yourbrand.com). Turn on Link decoration under Settings → Tracking: the script then adds lj_click_id to every link that points to your root domain or one of its subdomains.

  • Script installed on the subdomain too: it picks the ID up from the URL and stores it there – use the helper as usual.
  • No script on the subdomain: read the ID from the URL with the snippet under From the URL.

The booking system runs on a different domain (e.g. yourbooking.com). Link decoration leaves links to other domains untouched, so add the click ID yourself:

getClickId().then((clickId) => {
  if (!clickId) return;
  document.querySelectorAll('a[href^="https://yourbooking.com"]').forEach((link) => {
    const url = new URL(link.href);
    url.searchParams.set("lj_click_id", clickId);
    link.href = url.toString();
  });
});

On the booking domain, read lj_click_id from the URL (see From the URL) – or install the tracking script there with its own tracking domain, and it does that for you.

The booking system is embedded as an iframe. The embedded page has its own storage, separate from your site, so pass the click ID in the iframe's src:

<iframe id="booking" title="Book a call" width="100%" height="700"></iframe>

<script>
  getClickId().then((clickId) => {
    const src = new URL("https://yourbooking.com/embed");
    if (clickId) src.searchParams.set("lj_click_id", clickId);
    document.getElementById("booking").src = src.toString();
  });
</script>

Inside the booking page, read lj_click_id from its own URL and send it along with the booking.

Using Calendly, Cal.com, HubSpot or Typeform?

For supported tools, the script already passes the click ID into their embeds. This page is for your own booking system or form – for the others, see Apps.

Check that it works

  1. Open your site through a tracked link – a test ad URL or a link from the UTM Builder.
  2. Open the browser's developer tools and run in the Console:
    await window.lj.getClickId();
    window.lj.getTrackingStatus();
    Under Application → Local storage and Cookies you'll find the same ID as clickId.
  3. Submit a test booking and check that the record in your system has the click ID.
  4. Send the event and open the API Postbacks log to confirm it matched a click.

getTrackingStatus() returns one of these values:

StatusMeaning
pendingThe visit hasn't been reported yet.
trackedThe visit was recorded.
already_trackedLeadJourney already knew this visit.
skipped_internalNot reported: internal navigation, or a visit without an external referrer and without ad parameters.
skipped_reloadNot reported: a reload or back/forward navigation without ad parameters.
blocked_adblockerAn ad blocker stopped the request – a custom tracking domain helps.
blocked_botThe visit was classified as a bot.
failedThe request failed (network or server error).

Record the status with each submission

Add <input type="hidden" name="lj_tracking_status" /> to your form – the script fills it with the status, so you can see for every booking whether tracking worked.

FAQ