flowsk.com
Dogfood

We run our own acquisition through our own product

flowsk.com is instrumented with Flowsk, through the same public interface a customer's API calls use. Here is how, why the isolation boundary matters, and what we have committed to publish.

Jul 14, 2026· 4 min read ·Build in public
Quick answer

Signals::Internal → Signals::Client → the same API a customer calls

Every page on this site sets a durable server-set first-party id and records your visit as a journey in the same database a customer's data lands in. Our own instrumentation may only call the public facade — never the internal services — so there is no private path we could take that you could not.

We have also committed to publishing the resulting funnel. Not yet — see the last section for why, and for what would make publishing dishonest.

There is a specific kind of dishonesty that attribution vendors get away with: claiming accuracy in a category where nobody can check.

We cannot fully escape that yet. What we can do is remove every shortcut that would let us cheat, and say plainly what we will publish and when.

How this site is instrumented

Exactly the way we tell customers to do it:

A durable server-set id. A Rails before_action mints a UUID and sets _fsk_id as a first-party cookie in the HTTP response, with a two-year expiry. Safari’s seven-day cap on JavaScript cookies does not apply, because JavaScript never touches it.

Server-side pageviews. Every navigation — including every Turbo Drive visit — is a real GET to our server, so a background job records it. No dependency on JavaScript running at all, and prefetches and bot requests are filtered before the job is enqueued.

Server-confirmed identification. When someone joins a waitlist, uses the pixel checker, or signs in, our backend sends the identification event with the cause attached: join_waiting_list, monitor_signup, login. That cause appears on the person record and in the journey.

Server-confirmed billing. Stripe tells our backend that a subscription started; our backend records the purchase with the subscription id as the de-duplication key.

The isolation rule

This is the design decision we would most want to keep if we started over.

There is a hard boundary in the codebase:

  • The productSignals::* models and services, the public /api, the /app UI. Generic, multi-tenant, knows nothing about flowsk.
  • The dogfooding — everything under Signals::Internal::*. This is flowsk using the product, and it may only call the public Signals::Client facade. Never the internal services.

The rule: if the code is specific to flowsk — our lead capture, our tools, our billing — it is Internal. If it would serve any customer, it is product.

The payoff is that our instrumentation exercises exactly the contract a customer’s API call does. When we break the public interface, our own funnel breaks first — in production, on data we care about, immediately. That is a much better alarm than a test suite, because attribution bugs do not raise errors. They produce plausible wrong numbers, and plausible wrong numbers survive code review.

What we have committed to publish

A live page at /our-numbers with the real funnel: visitors, identified visitors, conversions, revenue, CAC and ROAS by first-touch campaign. Computed by Signals::Funnel — the same class that renders our customers’ reports. Not a screenshot, not a curated quarter.

Four rules, fixed before the first number goes up so they cannot be loosened later to make a month look better:

  1. Aggregate only. Channel and campaign rows plus totals. No person, no email address, no individual journey.
  2. No row about one person. Any channel below ten visitors is merged into a single aggregate row. At low volume a row is a person, and a published row about a person is a disclosure — regardless of whether it contains an email.
  3. Methodology on the page. First-touch credit, manually entered ad spend, imperfect bot filtering. A transparency page that hides its method is not one.
  4. It stays up. Including the bad months.

Why it is not live yet

Because we started this in August 2026, and the numbers would be near zero.

There is a version of this post that publishes an empty funnel and calls it principled. It would be theatre. A transparency page exists to be a commitment device — to make it costly for us to be wrong in future — and a table of zeros costs us nothing, proves nothing, and mostly demonstrates that nobody has bought anything yet.

There is also a trap in publishing early: you cannot un-publish credibly. If the page goes up at zero and comes down when it is still bad, that is a worse signal than never putting it up. The retraction is the message. So the honest sequence is to fix the rules first, publish when there is something to read, and then never take it down.

Which means the thing to judge us on today is not our numbers. It is whether the mechanism is real — and that part you can check: how the journey is stitched, why the cookie survives Safari, and how the same sale is counted once.

The numbers come when they are worth your time.

Frequently asked questions

Why does dogfooding matter for an attribution tool?

Because attribution fails silently. A bug does not raise an error, it produces a plausible wrong number. Running our own acquisition on the product means a broken contract shows up in our own funnel, in production, immediately — which is a better alarm than any test suite.

How is your dogfooding isolated from the product?

Everything flowsk-specific lives under Signals::Internal and may only call the public Signals::Client facade. The API controller a customer hits calls the same facade. Neither ever touches the internal services directly.

Do you use the same install mode you recommend?

Yes — the durable variant. Our Rails app sets the visitor id server-side and confirms identification and billing from the backend. The snippet reads the same cookie.

Why haven't you published your funnel yet?

Because we started in August 2026 and the numbers would be near zero. Publishing a table of near-zeros is theatre, not transparency. The page goes live when the funnel is worth reading, and then it stays up — including the bad months.

What will you publish, and what will you never publish?

Channel and campaign rows plus totals, with any channel below ten visitors merged into one aggregate row. Never a person, never an email address, never an individual journey. At low volume a row is a person, and a published row about a person is a disclosure.

See what we have committed to publish

The exact columns, the disclosure rules, and the reason the page is not live yet.

Read the commitment

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