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.
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:
- An API key — from your Lucia dashboard (Settings → API Keys).
- The SDK snippet — one
<script>tag that loads and auto-initializes Lucia. - The signals you want to capture — pageviews, clicks, conversions, and who the user is.
- (Web3 only) — a few lines that run when a visitor connects their wallet.
.envOn 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.
- 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 site | Call this | Why it matters |
|---|---|---|
| A page loads | (automatic) — or pageView('/path') on SPA route changes | The backbone of every journey |
| A visitor clicks a key button or link | buttonClick('id') (or auto-track) | Shows intent and which CTAs work |
| A purchase / signup / lead completes | trackConversion('tag', amount, {...}) | The outcome campaigns get credit for |
| A visitor logs in or signs up | userInfo('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:
- Automatic (no code)
- Manual (explicit)
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>
Call buttonClick from your own click handler when you want full control:
<button id="buy">Buy now</button>
<script>
document.getElementById('buy').addEventListener('click', () => {
LuciaSDK.buttonClick('buy-now', { text: 'Buy now', meta: { location: 'pricing' } });
});
</script>
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.ethereumreturns it as a hex string, so convert withparseInt(chainId, 16).walletName(optional) — e.g.'MetaMask','Phantom'.
What to do, and when
- On connect — after the user approves the connection, call
sendWalletInfowith their address and chain. - Tie the wallet to the person — if you also know their email/username, call
userInfoso the web2 and web3 identities merge into one profile. - 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>
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>
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
- Open your page and click around (connect a wallet if it's a dapp).
- 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>
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
- SDK API Reference → — every method and option.
- Button Click → — auto-tracking in depth.
- Track Conversion → — model the outcomes that matter.
- User Information → · Wallet Information → — unify web2 + web3 identity.