Skip to main content
Version: current (0.10)

Custom Website (HTML / JavaScript)

This guide is for any site where you can paste a <script> tag but don't have a framework build step — a hand-coded HTML site, a landing page, or custom-code blocks in Webflow, Framer, Shopify, WordPress, or a CMS. By the end you'll know exactly what to set up, how to install it, and how to capture the activity Lucia turns into attribution — for both web2 and web3 sites.

Using a framework instead?

If you're on Vite, React, or Next.js, start with those guides — Vite + TypeScript, React + JavaScript, Next.js — they pick up the same methods with framework-native wiring.


What you'll set up​

A quick checklist of everything this guide covers:

  1. An API key — from your Lucia dashboard (Settings → API Keys).
  2. The SDK snippet — one <script> tag that loads and auto-initializes Lucia.
  3. The signals you want to capture — pageviews, clicks, conversions, and who the user is.
  4. (Web3 only) — a few lines that run when a visitor connects their wallet.
No build step, no .env

On a static site there's nothing to npm install and no environment variables. Your API key is publishable — it's meant to live in the page, exactly like an analytics site ID. Keep your dashboard login private; the key in the tag is fine.


Step 1 — Add the SDK​

Paste this once, ideally in the <head> so it loads before your content. With the data-api-key attribute the SDK auto-initializes — there's nothing else to call to start collecting.

<script
src="https://cdn.luciaprotocol.com/lucia-sdk-latest.min.js"
data-api-key="YOUR_API_KEY"
></script>

That alone captures the pageview when the page loads. Everything below adds richer signals on top.

Where to paste it
  • Hand-coded site: in the <head> of every page (or a shared header/partial).
  • WordPress: a header/footer-scripts plugin, or your theme's "header code" box.
  • Webflow / Framer: Site Settings → Custom Code → Head.
  • Shopify: theme.liquid, inside <head>.

Step 2 — Capture web2 activity​

For a normal (web2) website, there are four signals worth sending. Here's which method maps to which real action:

When this happens on your siteCall thisWhy it matters
A page loads(automatic) — or pageView('/path') on SPA route changesThe backbone of every journey
A visitor clicks a key button or linkbuttonClick('id') (or auto-track)Shows intent and which CTAs work
A purchase / signup / lead completestrackConversion('tag', amount, {...})The outcome campaigns get credit for
A visitor logs in or signs upuserInfo('id', {...})Ties the journey to a real person

Pageviews​

Captured automatically on load. If your site swaps content without a full reload (a single-page flow), call pageView yourself when the view changes:

<script>
LuciaSDK.pageView(window.location.pathname);
</script>

Clicks​

Two options — pick whichever fits:

Add data-auto-track-clicks="true" to the SDK tag and Lucia tracks clicks on buttons, links, and role="button" elements for you:

<script
src="https://cdn.luciaprotocol.com/lucia-sdk-latest.min.js"
data-api-key="YOUR_API_KEY"
data-auto-track-clicks="true"
></script>

Give an element a stable name, add metadata, or exclude it:

<button data-lucia-track="signup-cta" data-lucia-meta-location="hero">Sign up</button>
<button data-lucia-ignore>Internal toggle</button>

Conversions​

Call trackConversion the moment an outcome completes — e.g. on your order-confirmation page or after a successful signup:

<script>
LuciaSDK.trackConversion('purchase', 49.0, {
currency: 'USD',
product_id: 'pro-plan',
quantity: 1,
});
</script>

Identify the user​

As soon as you know who the visitor is (after login or signup), associate their events so sessions across devices resolve into one profile:

<script>
LuciaSDK.userInfo('jane@example.com', { first_name: 'Jane', plan: 'pro' });
</script>

A complete web2 page​

Copy this into an index.html, swap YOUR_API_KEY, and open it — it tracks the pageview, a CTA click, a signup identity, and a conversion:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My Site</title>

<!-- 1. Load Lucia (auto-inits + auto-tracks clicks) -->
<script
src="https://cdn.luciaprotocol.com/lucia-sdk-latest.min.js"
data-api-key="YOUR_API_KEY"
data-auto-track-clicks="true"
></script>
</head>
<body>
<h1>Welcome</h1>

<!-- 2. A named CTA (auto-tracked thanks to data-lucia-track) -->
<button data-lucia-track="start-trial" data-lucia-meta-location="hero">Start free trial</button>

<!-- 3. A pretend signup + purchase flow -->
<button id="signup">Sign up</button>
<button id="checkout">Complete purchase</button>

<script>
// Identify the user once they sign up
document.getElementById('signup').addEventListener('click', () => {
LuciaSDK.userInfo('jane@example.com', { first_name: 'Jane' });
});

// Record the conversion when the purchase completes
document.getElementById('checkout').addEventListener('click', () => {
LuciaSDK.trackConversion('purchase', 49.0, { currency: 'USD', product_id: 'pro-plan' });
});
</script>
</body>
</html>

Step 3 — Capture web3 activity​

If your site is a dapp or has wallet connect, add one more signal: when a visitor connects a wallet, tell Lucia so on-chain activity joins the same customer profile as their web sessions. The key method is:

LuciaSDK.sendWalletInfo(walletAddress, chainId, walletName);
  • walletAddress — the connected account (e.g. '0x8f…21').
  • chainId — the numeric chain ID (Ethereum mainnet = 1, Linea = 59141). window.ethereum returns it as a hex string, so convert with parseInt(chainId, 16).
  • walletName (optional) — e.g. 'MetaMask', 'Phantom'.

What to do, and when​

  1. On connect — after the user approves the connection, call sendWalletInfo with their address and chain.
  2. Tie the wallet to the person — if you also know their email/username, call userInfo so the web2 and web3 identities merge into one profile.
  3. On change — re-send when the user switches account or network (accountsChanged / chainChanged).

Connect flow (EVM / MetaMask, etc.)​

<button id="connect">Connect wallet</button>
<script>
async function connectWallet() {
if (!window.ethereum) return alert('No EVM wallet found');

// 1. Ask the user to connect
const [address] = await window.ethereum.request({ method: 'eth_requestAccounts' });
const chainHex = await window.ethereum.request({ method: 'eth_chainId' });
const chainId = parseInt(chainHex, 16);

// 2. Send the wallet to Lucia
await LuciaSDK.sendWalletInfo(address, chainId, 'MetaMask');

// 3. (Optional) merge with a known user identity
// await LuciaSDK.userInfo('jane@example.com', { wallet: address });
}

document.getElementById('connect').addEventListener('click', connectWallet);

// 4. Re-send if the user switches account or network
if (window.ethereum) {
window.ethereum.on('accountsChanged', async ([address]) => {
if (!address) return;
const chainId = parseInt(await window.ethereum.request({ method: 'eth_chainId' }), 16);
await LuciaSDK.sendWalletInfo(address, chainId, 'MetaMask');
});
window.ethereum.on('chainChanged', async (chainHex) => {
const accounts = await window.ethereum.request({ method: 'eth_accounts' });
if (accounts[0]) await LuciaSDK.sendWalletInfo(accounts[0], parseInt(chainHex, 16), 'MetaMask');
});
}
</script>
Detecting wallets reliably

Multiple extensions can clash on window.ethereum. The SDK ships EIP-6963 helpers — detectEvmProvider() and detectSolanaProvider() (see the SDK API Reference) — to pick the right provider when a visitor has several wallets installed. LuciaSDK.checkMetaMaskConnection() returns whether MetaMask is already connected, so you can re-send wallet info on page load without prompting.

A complete web3 page​

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My Dapp</title>
<script
src="https://cdn.luciaprotocol.com/lucia-sdk-latest.min.js"
data-api-key="YOUR_API_KEY"
data-auto-track-clicks="true"
></script>
</head>
<body>
<h1>My Dapp</h1>
<button id="connect">Connect wallet</button>
<button id="mint" data-lucia-track="mint-cta">Mint</button>

<script>
async function connectWallet() {
if (!window.ethereum) return alert('No EVM wallet found');
const [address] = await window.ethereum.request({ method: 'eth_requestAccounts' });
const chainId = parseInt(await window.ethereum.request({ method: 'eth_chainId' }), 16);
await LuciaSDK.sendWalletInfo(address, chainId, 'MetaMask');
}
document.getElementById('connect').addEventListener('click', connectWallet);

// Record a conversion when a mint succeeds
document.getElementById('mint').addEventListener('click', async () => {
// ...your mint logic...
await LuciaSDK.trackConversion('mint', 0.08, { currency: 'ETH', collection: 'genesis' });
});

// Reconnect silently if the wallet is already authorized
if (window.ethereum) {
window.ethereum.on('accountsChanged', async ([address]) => {
if (!address) return;
const chainId = parseInt(await window.ethereum.request({ method: 'eth_chainId' }), 16);
await LuciaSDK.sendWalletInfo(address, chainId, 'MetaMask');
});
}
</script>
</body>
</html>
Solana wallets

The SDK supports EVM and Solana wallets. For Solana (e.g. Phantom), send the public key with the provider name — sendWalletInfo(address, { provider: 'Phantom' }) — and use detectSolanaProvider() to find the wallet. See Connecting Wallets for the full flow.


Step 4 — Verify it's working​

  1. Open your page and click around (connect a wallet if it's a dapp).
  2. In the Lucia dashboard, open Insights — your pageview, click, conversion, and wallet events should appear within a few seconds.

Turn on console logging while you test by initializing manually with debug: true instead of (or alongside) the auto-init tag:

<script src="https://cdn.luciaprotocol.com/lucia-sdk-latest.min.js"></script>
<script>
LuciaSDK.init({ apiKey: 'YOUR_API_KEY', debug: true });
</script>
What's captured, and privacy

Auto-tracking records the clicked element's tag and a short label, and automatically skips password and credit-card fields and anything marked data-lucia-ignore. See Privacy & Compliance for the full list.


Next steps​