# Cookie Policy

> Every cookie the portal sets, the browser storage beside it, and the two things the consent banner actually asks your permission for.

Source: https://hyperjobs.io/legal/cookies

This page lists every cookie the HyperJobs portal sets, the browser storage it
uses alongside them, and the two things we ask your permission for. Most of what
is here is strictly necessary to run the site. The parts that are not, we ask
about first.

## The short version

There **is** product analytics on this site: **PostHog**, which we use to measure
how the docs and the signup flow are actually working. It goes in the first line
because it is the thing you would most want to know, and burying it four headings
down would be the sort of decision this page exists not to make.

There are **no advertising cookies and no cross-site tracking** here. There is no
ad network, no ad pixel, and no third-party tracker of any kind. Nothing is sold,
and nothing is shared with anyone for their own purposes.

Every cookie in the table below **except PostHog's** is **strictly necessary** —
under the ePrivacy Directive, Article 5(3), as implemented in Italy, those are
exempt from consent, because there is nothing to decide. Switching off the cookie
that signs you in just means not being signed in. PostHog's cookie is the
opposite: nothing breaks without it, so we ask, and it is not set unless you say
yes.

The banner asks about the two things that genuinely are your call:

- **Product analytics.** With your consent, PostHog stores an id on this device,
  links what you read before signing up to your account afterwards, and records
  session replays. Without your consent it still counts pages — but stores
  **nothing** on your device, builds no profile, and never receives your IP.
  Both modes are described below, including what the second one still does.
- **Abuse prevention** — the device fingerprint taken at signup and sign-in to
  catch bulk account farming. Not a cookie, but Article 5(3) covers *reading*
  information from your device as well as storing it, and a canvas and GPU probe
  is squarely that.

<Note>
  Decline either and the thing it gates never happens on your device: no `ph_*`
  cookie is written, no replay is recorded, no fingerprint is computed — the code
  checks before it runs, not after. Nothing about your account changes and no
  feature is withheld. "Reject" works exactly as well as "accept", and you can
  change your mind from **Cookie settings** in the footer at any time.
</Note>

<Warning>
  Two things "reject" does **not** stop, stated here rather than left for you to
  find in a network tab. Anonymous page counts continue, because they touch
  nothing on your device — see the analytics section below. And our own backend
  keeps recording what your *account* does: that you signed up, created a key, or
  bought a plan. That is the database we run the business on, it never involves
  your device, and declining a cookie cannot un-sell a subscription. The
  [Privacy Policy](/legal/privacy) gives the legal basis for both.
</Warning>

The banner appears in the EU/EEA, the UK, and Switzerland, where opting in is
required. Elsewhere the same controls live in the footer instead of interrupting
you. If we cannot tell where you are, you get the banner — we would rather show
it unnecessarily than skip it wrongly.

The API at `api.hyperjobs.io` sets no cookies at all. It authenticates with API
keys. If you only use the API and never open the portal, none of this touches you.

## Cookies we set

| Cookie | Purpose | Lifetime | Flags | Category |
| --- | --- | --- | --- | --- |
| `__session` | The Firebase session cookie. This is what keeps you signed in. Without it there is no authenticated portal. | 5 days | `httpOnly`, `secure`, `sameSite=lax` | Strictly necessary |
| `__csrf` | A double-submit CSRF token. Page JavaScript reads it and echoes it back as a header, which proves a state-changing request came from our page and not from someone else's. | 5 days | `secure`, `sameSite=strict`, readable by JS by design | Strictly necessary |
| `__session_hint` | A flag saying "someone is signed in", so the header can render the right button on first paint instead of flashing a placeholder. A UI hint only — never trusted for any authentication decision, server-side or otherwise. | 5 days | `secure`, `sameSite=lax` | Strictly necessary |
| `portal_country` | Your country, taken from Cloudflare's `cf-ipcountry` edge header. Set only on authenticated and auth-flow routes, so public pages stay cacheable. | 24 hours | `secure`, `sameSite=lax`, not `httpOnly` | Strictly necessary / functional |
| `sidebar_state` | Whether you collapsed the dashboard sidebar, so it stays how you left it. | 7 days | `sameSite` default, readable by JS | Functional |
| `ph_<project-key>_posthog` | PostHog's own cookie: the random id that lets us tell this browser's visits apart from another's, plus the current session. **Set only if you consent to product analytics.** Decline and PostHog holds everything in memory instead — this cookie is never written. | 365 days (PostHog's default; we do not override it) | `secure`, `sameSite=lax`, readable by JS | Analytics — **consent** |

All of them are first-party: they are set on our own domain, and none is readable
by another site. `__session` is `httpOnly`, so page JavaScript cannot read it
even on our own site; the rest are readable by JavaScript because the code that
needs them runs in your browser.

One honest qualification on `ph_<project-key>_posthog`. The cookie itself is
first-party and nobody else can read it — but the id inside it is what identifies
your events to PostHog, our analytics processor, so unlike every other row in
this table its contents do leave our systems. PostHog is bound by a data
processing agreement and processes it only for us; it is listed in
[Subprocessors](/legal/subprocessors). No other cookie here is shared with
anyone.

The `secure` flag is set in production. In local development it is not, because
`localhost` is not served over HTTPS.

## Browser storage we use

Not cookies — no browser attaches this to a request on its own — but it is stored
on your device, so it belongs on this page. Two of the rows below are read by our
own code and sent somewhere deliberately; both say so.

| Key | Storage | Purpose |
| --- | --- | --- |
| `hyperjobs-cookie-consent` | localStorage | Your answer to the banner, and when you gave it. Kept on your device rather than in our database — it is a record about a browser, and this is the browser. |
| `hyperjobs-ui-theme` | localStorage | Whether you chose light, dark, or system theme. Read before first paint so the page does not flash white. |
| `hyperjobs-docs:recent` / `hyperjobs-help:recent` | localStorage | Your recent searches in the docs and help search boxes, shown when you reopen them. Stays in your browser. |
| `portal_session_id` | localStorage | An id for this browser's device session, sent as a header when you sign in so the security page in your dashboard can show you this device in your session list. |
| `ph_<project-key>_posthog` | localStorage | PostHog's copy of the same id and session state as its cookie above, and what it needs to hold a session replay together. Its contents go to PostHog with each event. **Written only if you consent to product analytics** — decline and PostHog runs with `persistence: 'memory'`, which means its id lives in the tab and disappears when you close it, and nothing is written here at all. |

Clearing your browser storage clears all of it. You lose your theme choice and
your recent searches, we will ask about analytics and the fingerprint again, and
PostHog stops recognising this browser; nothing breaks.

## Why each of these is strictly necessary

"Strictly necessary" means necessary to deliver a service you actually asked for
— not necessary for our business. It covers the cookies in the first table except
`ph_<project-key>_posthog`, which is not necessary for anything and is therefore
never set unless you say yes.

- `__session` **is** the sign-in. You asked to sign in.
- `__csrf` protects your account from a third-party site making requests as you.
  Security of the service you asked for.
- `__session_hint` exists so the portal renders correctly and fast without extra
  database reads. It carries no identifier of you beyond "signed in".
- `portal_country` is a country code from an edge header, used for
  country-dependent rendering. It is not a location trace: it is two letters, it
  lasts a day, and we already saw your IP when you connected.
- `sidebar_state` remembers a preference you expressed by clicking a button. It
  is a functional cookie in the narrow sense — it holds a UI setting and nothing
  else. It contains no identifier at all, so it cannot be used to recognise you.

None of these follows you to another site, and none of them can. There is no
third-party cookie, no advertising pixel, and no fingerprinting for advertising
anywhere on this site.

## What about product analytics?

This is the half of the banner most worth reading, because it is the half that
keeps doing something after you say no.

We use **PostHog** to answer questions our database cannot: how many people read
the quickstart, which page they left from, what they searched the docs for and
did not find. Our own Postgres knows what accounts do. It knows nothing about the
reader who never became an account, and that reader is the one we most need to
understand.

**It is proxied through our own origin.** Your browser talks to
`hyperjobs.io/ingest`, not to `posthog.com`. That is a plain reverse proxy, and
it is there because a large share of a *developer* audience runs an ad-blocker
that blacklists analytics domains — unproxied, the numbers would be skewed
towards the people least like our users. Being straight about what it means: the
proxy changes who your browser connects to, **not** who receives the data.
PostHog still processes it, on their **EU cloud in Germany**. They are a
subprocessor, and they are [listed as one](/legal/subprocessors).

### With your consent

PostHog runs in its full mode: the `ph_*` cookie and localStorage above, a
person profile, `identify()` linking your anonymous browsing to your account once
you sign in, and **session replay**.

### Without your consent

PostHog still runs, and we would rather tell you that than have you find it. It
runs in a different mode, and the difference is the whole argument:

- **`persistence: 'memory'`** — nothing is written to your device. No cookie, no
  localStorage. The id it uses exists in the tab and dies with it.
- **It never calls `identify()`** — nothing is ever linked to your account.
- **`person_profiles: 'identified_only'`** — no profile is created for you at
  all. There is no record to look you up in.
- **`ip: false`** — PostHog does not receive your IP address (this is true in
  *both* modes).
- **No session replay.**

That is anonymous, aggregate page counting. Our basis for it is **legitimate
interests** (Art. 6(1)(f) GDPR) rather than consent, and the reason we do not ask
is that there is nothing to ask about under **Article 5(3) of the ePrivacy
Directive**: that Article governs storing information on your device or reading
information from it, and this mode does neither. It touches your device exactly
as much as our web server's access log does, which is not at all.

### Session replay records your screen

A developer will ask, so: yes, in the consented mode, replay reconstructs what
happened in your browser window. Two things constrain it, and both are code, not
policy:

- **Every form input is masked.** Replay never captures what you type into a
  field — `maskAllInputs`, on.
- **The "copy your key" dialog is blocked entirely.** The one moment a full API
  key is ever visible in the portal, replay is not looking: that dialog renders
  the key in a code block, which input-masking would *not* have covered, so the
  subtree is excluded from capture outright. This is deliberate and load-bearing
  — without it, every key we issue would be recorded in plaintext at the exact
  moment it is revealed.

### We record what you search the docs for

The docs and help search boxes send the text you typed to PostHog, as a property
on a `docs_search` event — and a `docs_search_empty` event when your search
returned nothing. In both modes: the anonymous mode still counts these, without
storing anything on your device and without a profile to attach them to.

We are telling you rather than burying it, and the justification is honest: this
is a search box on public documentation, not a form. What people type into it is
`salary_annual_max`. The searches that return **nothing** are the single most
useful thing this product collects — every one is a developer who wanted
something our documentation did not have, or had under a name they did not guess.
That is a to-do list written by users. If you would rather not contribute to it,
do not use the search box; the pages are all navigable without it.

## What about the device fingerprint?

The other thing the banner asks about, and the one people assume the worst about.
It deserves a straight answer.

At signup and sign-in we compute a device fingerprint for **abuse prevention** —
stopping one person from farming free accounts. It uses the open-source
FingerprintJS library, which **runs entirely in your browser and sends nothing to
FingerprintJS**. It stores nothing on your device; it reads characteristics your
browser already exposes (screen, timezone, fonts, GPU) and hands the result to
us.

Three things worth stating plainly:

- **It is never used for advertising, analytics, or cross-site tracking.** It is
  read when a human investigates suspected abuse. It blocks nothing
  automatically, and no decision about your account is ever made by a machine on
  the strength of it.
- **We ask, rather than claim an exemption.** There is a respectable argument
  that fraud prevention is strictly necessary to provide a metered service, and
  we could have relied on it. We would rather ask: the exemption is contested,
  and asking costs you one click and costs us a signal we can live without.
- **It runs only when you sign up or sign in** — not on the public site, not in
  the docs, not on this page, and not while you browse. The library is not even
  loaded until you open the sign-in form.

Decline it and nothing else changes. The [Privacy Policy](/legal/privacy)
explains what we store, for how long, and how to object.

## Third parties involved in storage on your device

We embed no advertising trackers. These providers are the ones with a hand in
what lands on your device, either because you deliberately used them or because
you consented to analytics:

| Provider | When | What for |
| --- | --- | --- |
| PostHog | Any page, if you consented to product analytics | The `ph_<project-key>_posthog` cookie and localStorage above. Set by our own page on our own domain, not by a PostHog script from a PostHog domain — but the id in it goes to PostHog, who process the events for us on their EU cloud |
| Google (Firebase Authentication, reCAPTCHA Enterprise) | Signing in, especially with Google; bot protection on auth | The sign-in flow, and telling a human from a script |
| Stripe | Opening checkout or the billing portal | Payment processing and fraud prevention |
| Cloudflare | Any request | Edge security and delivery |

Apart from PostHog's, those cookies are set by those providers under their own
policies, in the course of doing the thing you asked for.
[Subprocessors](/legal/subprocessors) lists who they are and what they receive.

## Managing cookies

**Cookie settings** in the footer reopens the banner on any page, so you can
change or withdraw your answer whenever you like. Withdrawing takes effect
immediately, not at your next visit: session replay stops, the person link is
reset, PostHog's `ph_*` keys are cleared from this browser, and it drops back to
the memory-only mode described above. Anything already computed is dropped there
and then.

Your answer is stored in your own browser (`hyperjobs-cookie-consent` in
localStorage), not in our database. Clearing your browser storage clears it, and
we will ask again.

You can also block or delete cookies in your browser settings. If you block ours,
sign-in stops working — `__session` **is** the sign-in, so removing it logs you
out. That is the trade, and it is your call to make. Blocking `sidebar_state`,
blocking the `ph_*` cookie, or clearing localStorage costs you nothing but a
preference.

**"Do Not Track" and Global Privacy Control.** We do not currently act on either
signal, and this page used to say we honoured them trivially because we were not
doing the thing they turn off. That is no longer true, so here is the accurate
version: PostHog's SDK can be configured to respect `DNT` and we have not
switched it on. The banner is the control that actually works, and it works
completely — choose **Essential only** and nothing is stored on your device.
Neither signal has any bearing on selling or sharing your data, because we do
neither. In the EU/EEA, the UK, and Switzerland nothing is stored until you opt
in anyway.

## Changes

If we add a cookie, this page changes first, and it will say what the cookie does
and which category it is in. One that needs consent goes in the banner as its own
choice, off by default, and we ask before setting it rather than after.

That is the rule analytics itself went through. It is a new category rather than
a wider reading of an old one, so any stored answer is invalidated and the
question asked again from a clean slate: an "accept" given to a banner that never
mentioned PostHog is not consent to PostHog. If you are asked something you
thought you had already answered, that is why, and it is deliberate.

Questions: <Email kind="dpo" subject="Cookie Policy" />.
