Install LeetLytics in one minute.
Start with a public workspace key in the browser for journey-step tracking. Keep server-side conversion keys on your backend only. Never expose the conversion API key in browser code.
Copy-paste browser snippet
Use this for landing pages, Webflow, Google Tag Manager, or any alpha account that needs the fastest path. It automatically sends a site_activity/page_view event with the query-free current page path so the first useful dashboard insight appears without journey setup.
<script>
(function () {
var config = {
workspaceKey: "NEXT_PUBLIC_LEETLYTICS_WORKSPACE_KEY",
endpoint: "https://leet.fyi/api/ingest",
breakageEndpoint: "https://leet.fyi/api/breakages",
maxQueuedEvents: 100
};
var queue = [];
function compact(value) {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function targetFromFetchInput(input) {
var raw = typeof input === "string" ? input : input && input.url;
if (!raw) return "/";
try {
return new URL(raw, window.location.href).pathname || "/";
} catch (error) {
return String(raw).split("?")[0].split("#")[0] || "/";
}
}
function currentPagePath() {
return (window.location && window.location.pathname) || "/";
}
function normalizeEventName(name) {
return compact(name).toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 128);
}
function shouldIgnoreFetchTarget(target) {
return target === "/api/ingest" || target === "/api/breakages" || target === "/api/conversions";
}
function reportBreakage(event) {
return fetch(config.breakageEndpoint, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
workspaceKey: config.workspaceKey,
type: event.type,
statusCode: event.statusCode,
method: event.method,
target: event.target,
source: "fetch",
occurredAt: new Date().toISOString()
}),
keepalive: true
}).catch(function () {});
}
if (typeof window.fetch === "function") {
var originalFetch = window.fetch.bind(window);
window.fetch = function leetlyticsFetch(input, init) {
return originalFetch(input, init).then(function (response) {
var target = targetFromFetchInput(input);
if (response.status >= 400 && response.status <= 599 && !shouldIgnoreFetchTarget(target)) {
reportBreakage({
type: "request",
statusCode: response.status,
method: compact(init && init.method) || compact(input && input.method) || "GET",
target: target
});
}
return response;
});
};
}
function send(event) {
var payload = {
workspaceKey: config.workspaceKey,
journey: compact(event.journey),
step: compact(event.step),
status: compact(event.status),
occurredAt: new Date().toISOString(),
pagePath: event.pagePath || currentPagePath(),
anonymousId: compact(event.anonymousId),
sessionId: compact(event.sessionId),
attribution: event.attribution,
metadata: event.metadata
};
if (!payload.journey || !payload.step || !payload.status) {
return Promise.reject(new Error("LeetLytics journey, step, and status are required"));
}
return fetch(config.endpoint, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload),
keepalive: true
}).catch(function (error) {
queue.push(payload);
if (queue.length > config.maxQueuedEvents) queue.shift();
throw error;
});
}
window.leetlytics = window.leetlytics || {};
window.leetlytics.trackJourneyStep = send;
window.leetlytics.trackEvent = function (event) {
var name = event && event.name;
var step = normalizeEventName(name);
if (!step) return Promise.reject(new Error("LeetLytics event name is required"));
return send({
journey: "key_actions",
step: step,
status: "completed",
pagePath: event.pagePath,
anonymousId: event.anonymousId,
sessionId: event.sessionId,
attribution: event.attribution,
metadata: event.metadata
});
};
window.leetlytics.queuedEvents = function () { return queue.slice(); };
window.leetlytics.trackJourneyStep({ journey: "site_activity", step: "page_view", status: "completed" });
})();
</script>NPM SDK usage
The SDK trims labels, automatically attaches the current query-free page path, rejects invalid statuses, sends to /api/ingest, can auto-report 400/500 fetch responses to /api/breakages, shows simple Important events, and bounds offline retries.
pnpm add @leetlytics/sdk
import { createLeetLyticsClient, installFetchBreakageTracking } from "@leetlytics/sdk";
export const leetlytics = createLeetLyticsClient({
workspaceKey: process.env.NEXT_PUBLIC_LEETLYTICS_WORKSPACE_KEY!,
endpoint: "https://leet.fyi/api/ingest",
maxQueuedEvents: 100,
});
if (typeof window !== "undefined") {
installFetchBreakageTracking(leetlytics);
}
await leetlytics.trackEvent({
name: "signup_click",
attribution: { utmSource: "newsletter", utmCampaign: "alpha-launch" },
});
await leetlytics.trackJourneyStep({
journey: "signup",
step: "account_created",
status: "completed",
attribution: { utmSource: "newsletter", utmCampaign: "alpha-launch" },
});Next.js example
// app/signup/page.tsx or a small client component
"use client";
import { createLeetLyticsClient } from "@leetlytics/sdk";
const leetlytics = createLeetLyticsClient({
workspaceKey: process.env.NEXT_PUBLIC_LEETLYTICS_WORKSPACE_KEY!,
});
export function SignupStartedTracker() {
return <button onClick={() => leetlytics.trackJourneyStep({
journey: "signup",
step: "form_submitted",
status: "completed",
})}>Create account</button>;
}Google Tag Manager / Webflow
Google Tag Manager / Webflow
1. Paste the browser snippet before </body> or in a Custom HTML tag.
2. Fire this on a successful form submit or button click:
window.leetlytics.trackJourneyStep({
journey: "signup",
step: "form_submitted",
status: "completed",
attribution: {
source: "landing-page",
utmSource: new URLSearchParams(location.search).get("utm_source") || undefined,
utmCampaign: new URLSearchParams(location.search).get("utm_campaign") || undefined
}
});Shopify app
Shopify installs through OAuth and activates a storefront web pixel with public settings only. Free stores track page, product, and cart activity; checkout-started and checkout-completed attribution stay gated to paid plans.
Shopify app
1. Sign in to LeetLytics and open /api/shopify/auth?shop=your-store.myshopify.com.
2. Approve the app install so LeetLytics can create the storefront web pixel.
3. Free tracks page_viewed, product_viewed, and cart_viewed events.
4. Paid plans can enable checkout_started and checkout_completed attribution.
The web pixel receives only public settings:
{
workspaceKey: "public workspace key",
appUrl: "https://leet.fyi",
plan: "free | pro | scale",
enableCheckoutEvents: false
}Public distribution and live provider activation are separate handoff steps; this install path should never expose Shopify access tokens or private conversion keys to the storefront.
WordPress plugin
The WordPress plugin starts free: site owners paste a public workspace key under Settings -> LeetLytics, then enable page views, important click events, and frontend breakage reporting. Server-side conversions still use backend-only keys.
WordPress plugin
1. Install the LeetLytics plugin zip.
2. Open Settings -> LeetLytics.
3. Paste the public workspace key from your dashboard.
4. Leave Free tracking enabled for page views, important clicks, and fetch breakages.
Use data-leetlytics-event on important buttons:
<button data-leetlytics-event="signup_click">Start</button>
Keep conversion API keys on your backend only.Use the dashboard to confirm first signal, upgrade through pricing when volume or checkout attribution needs paid plan capabilities, and keep release submission separate from local plugin packaging.
Server-side conversion attribution
Conversion revenue belongs on your backend. The customer API key is hashed in LeetLytics and must only be used server-side.
// Authorization: Bearer ***
await fetch("https://leet.fyi/api/conversions", {
method: "POST",
headers: {
"content-type": "application/json",
"Authorization": "Bearer " + process.env.LEETLYTICS_CONVERSION_API_KEY!
},
body: JSON.stringify({
workspaceKey: process.env.LEETLYTICS_WORKSPACE_KEY,
conversionId: checkout.session.id,
amountCents: 120000,
currency: "USD",
journey: "checkout",
anonymousId: customer.anonymousId,
occurredAt: new Date().toISOString()
})
});