Skip to content

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:

  1. 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 www twin is stored beside it.
  2. 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.