flowsk.com
Getting started

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.

Aug 4, 2026· 3 min read ·Docs
Quick answer

<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.

Check my install

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.

Keep reading