Documentation
How data gets in
The tag, the package, and what arrives with no code at all.
The tag, the package and the connections all land on the same record. Somebody reads your pricing page on Monday without saying who they are, leaves an address on Wednesday, signs up in your app on Friday and pays a month later: that's one person with one timeline, and none of the four things that recorded it had to know about the others.
Nothing here needs all of it on day one. The tag alone is useful within a minute.
Your website: one script tag
Settings → Connections → Your website shows the tag, already carrying your own public key:
<script async src="https://www.kicklace.com/api/in/YOUR_PUBLIC_KEY/track.js"></script>
It goes in the <head> of every page. From then on it records page reads, remembers how each visitor
arrived (the campaign, the referrer, the ad click id), and can hear your signup forms without you
touching them. There are no cookies. Your website and its forms has the steps
for each site builder; The tag is everything it does and everything you can call on
it.
Two things to do on that screen before the tag can post anything:
- Say where your site is. Kicklace only takes posts from addresses you've listed, so a stranger
can't write into your workspace from theirs. The
wwwtwin is stored beside it. - Watch for the first visit. The band under the tag turns green by itself when a page read arrives.
Your servers: one line of code
Your website knows about traffic. Only your own code knows that somebody signed up, got started, or paid, so that half is a package:
npm install kicklace
import { Kicklace } from "kicklace";
const kl = new Kicklace(process.env.KICKLACE_SECRET_KEY!);
await kl.track("signup", { email });
await kl.track("purchase", { email, value: 49, idempotencyKey: invoice.id });
It has no dependencies, runs anywhere fetch does, and never throws when Kicklace refuses something:
a refusal comes back as { ok: false, error, message }, with the sentence to act on inside it. The
kicklace package is every method; Events is every name you can post and what
each one moves.
The package adds no address of its own. Everything it does is POST /api/v1/events with your secret
key as a bearer token, so any language that can make an HTTP request can do the same:
curl -X POST https://www.kicklace.com/api/v1/events \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"type":"purchase","email":"ada@example.com","value":49,"idempotency_key":"in_123"}'
Where a GitHub App is set up for this deployment, you don't have to write even that by hand: install it on your repository from Connections → GitHub, and Setup's Hear it step opens a pull request that wires the tag or the package in for you. You review it and merge it. Kicklace never pushes to your default branch itself.
What arrives with no code at all
Some of the most useful events are ones you never have to send, because the service they happen in posts them for you:
| Connection | What lands |
|---|---|
| Stripe | payments, refunds, subscriptions, trials, checkouts left behind |
| Clerk or Kinde | a new account is a sign-up; a sign-in, if you want it |
| Vercel | every production deploy, as a release |
| GitHub | sponsors as purchases, issues as support messages, stars if you ask |
| Your database | the rows in your users table, as sign-ups |
A Stripe payment lands as the same purchase your own server would have posted, so a sequence at
Paying on your Lifecycle map answers it without knowing Stripe exists.
Where the keys are
There are two, and they aren't interchangeable.
| Key | Shape | Where it belongs |
|---|---|---|
| Public key | wk_… |
in the tag, in a page's source; it's public on purpose |
| Secret key | sk_live_… |
on a server, in an environment variable, never in browser code |
Make and revoke secret keys under Connections → Your website → Developers, or on Connections → Your servers, which also has your website's heartbeat, the outgoing delivery log, the rate limits and the four environment names. A secret key is shown once and stored as a hash; if you lose it, revoke it and make another.
The four names the package and the tag read:
| Name | What it is |
|---|---|
KICKLACE_SECRET_KEY |
your secret key, on the server only |
NEXT_PUBLIC_KICKLACE_KEY |
your public key, for the tag |
KICKLACE_URL |
where Kicklace is, if not https://www.kicklace.com |
NEXT_PUBLIC_KICKLACE_URL |
where the tag is served from, if not the default |
What Kicklace already knows about your app
Once Vercel and GitHub are both connected, Kicklace reads your production repository (the framework, the packages, the environment variable names) and says what it found: "Stripe is installed," "You already have PostHog." So you connect what you already run before pasting a key for anything new.
Setup can go further: it reads the events your own code has posted, your Stripe prices, and a paragraph from your README, and proposes what look like your important customer actions. Uncheck what's wrong, press Looks good, and your Lifecycle reads the events you confirmed from then on.
Set it up by asking
Kicklace speaks the Model Context Protocol, so you can point Claude at a workspace and set it up in conversation. It never sends an email and never deletes anything; a person does that. The MCP server is the protocol, and Use Kicklace from Claude is the screen and what to ask it. Ask Kicklace is the same thing inside the app.
One command, for a Next.js project
Setup's Hear it step gives you one line to run in a Next.js project you already have:
npx kicklace init --setup <token>
It adds <KicklaceTag /> to your root layout and kicklace to your dependencies, writes this
workspace's keys into .env.local (a name already there is left alone, and the secret key is never
printed), and says each thing as it does it. The token lasts 15 minutes and works once. Run it twice
and the second run changes nothing. The kicklace package has the rest.