Install the snippet
One script tag before the closing head tag. Pageviews, clicks and form submits start flowing immediately, and the JavaScript API is available on window.flowsk.
<script async src="https://flowsk.com/flowsk.js" data-write-key="pk_…"></script>
Paste it before </head> and you are tracking. The write key is public by design — it can only write events to your property, never read them.
This gets you the same 7-day Safari lifetime every JavaScript tracker has. See proxy setup for the durable version.
The tag
<script async src="https://flowsk.com/flowsk.js" data-write-key="pk_live_…"></script>
Put it in <head>, before your other tags. Get your write key from /app/install in your account.
That is the whole install. From this point you are recording pageviews, clicks on links and buttons, and form submissions, all bound to an anonymous visitor id.
What it captures automatically
| Event | When it fires | Recorded as |
|---|---|---|
$view |
Page load, Turbo/Turbolinks navigation, back/forward | navigation |
$click |
Click on a, button, input[type=submit], or [data-fsk-track] |
navigation |
$submit |
Any form submission | navigation |
Click events carry the element’s tag, id, class, visible text and href, plus the nearest [data-section] ancestor — so you can label regions of a page without writing any tracking code:
<section data-section="hero">
<a href="/pricing">See pricing</a> <!-- recorded with section: "hero" -->
</section>
Pageviews are de-duplicated by URL, so a framework that fires both a native load and its own “loaded” event does not double count. Speculative prefetches do not fire Turbo’s load event at all, so they are never counted as visits.
The JavaScript API
// Bind the anonymous visitor to a person. Call it the moment you have an email.
flowsk.identify("hello@example.com", { plan: "pro" })
// Record a named business action (allow-listed names only, see below).
flowsk.track("checkout_started", { cart_value_cents: 12900 })
// Record a purchase from the browser. Always pair with a server-side event.
flowsk.conversion("hello@example.com", 12900, "order_10482")
// The current anonymous id — useful for passing into iframes or checkout metadata.
flowsk.anonymousId()
// Manually record a pageview (React/Vue routers).
flowsk.page()
Allow-listed event names
track() only records names the product recognises, so a typo cannot quietly create a new event type nobody notices:
| Category | Names |
|---|---|
navigation |
tool_used |
identification |
signup_started, signup_completed, lead_captured, waitlist_joined, demo_requested, trial_started, report_sent, login |
purchase |
checkout_started, checkout_completed, subscription_started, subscription_cancelled, add_to_cart, upgraded |
System events ($view, $click, $submit, $change) are sent by the snippet itself.
SPA routers
Turbo and Turbolinks work with no extra code. For a client-side router, call flowsk.page() after each navigation:
// Next.js App Router
"use client"
import { usePathname, useSearchParams } from "next/navigation"
import { useEffect } from "react"
export function FlowskPageviews() {
const pathname = usePathname()
const search = useSearchParams()
useEffect(() => { window.flowsk?.page() }, [pathname, search])
return null
}
Identify as early as you honestly can
The snippet’s anonymous id lives in localStorage, which Safari evicts after seven days of inactivity. Until you have an email, that is your whole memory of the visitor.
The moment identify() runs, every session that visitor ever had is bound to a person — and the journey stops depending on browser storage. On sites where buyers take more than a week to decide, an early email capture is worth more than any tracking configuration.
Call it wherever an email legitimately arrives: newsletter signup, account creation, checkout, gated download.
Then make it durable
Everything above is client-side, which means it inherits the same seven-day Safari lifetime as every other JavaScript tracker. To lift that limit, your own server has to set the id — see proxy setup.
And whatever install mode you use, send purchases from your backend too. See the events API and de-duplication.
Frequently asked questions
Is the write key secret?
No, and it does not need to be. It grants write-only access to one property. It cannot read your data, change settings or authenticate anyone. It is designed to sit in public HTML, exactly like a Google Analytics measurement id.
Where exactly should the tag go?
In the head, before other tags. It is async so it never blocks rendering, and being early means it captures the landing URL and click ids before any redirect or consent script can alter them.
Does it work with Turbo, Turbolinks or an SPA?
Turbo and Turbolinks navigations are handled automatically, as are browser back/forward. For a React or Vue router, call flowsk.page() on route change.
Will it slow my site down?
It is a few KB, loads async, and posts with sendBeacon as a CORS simple request — no preflight round trip, no render blocking, and nothing on the critical path.
What if the request fails?
It fails silently. Tracking must never break the page, so every send path is wrapped and errors are swallowed.
Verify it fired
The pixel health checker fetches your page from our server and tells you which tags are actually present in the HTML — including this one.
Stop guessing which ad made the sale.
Flowsk Signals stitches the anonymous click to the email to the purchase — first-party, server-side, de-duplicated. One snippet, $29/mo, and every conversion comes with a receipt you can inspect.