Pixelex.Consent (Pixelex v0.1.0)

Copy Markdown View Source

Whether this visitor may be tracked at all, and by which layer.

Two questions that get conflated and must not be:

  • first-party analytics — a cookieless, rotating-hash pageview stored in the site's own database. Nothing is written to or read from the device, so ePrivacy Article 5(3) — the cookie rule — is not engaged, and no banner is required for it. GDPR still governs the processing; the lawful basis is legitimate interest under Article 6(1)(f), available precisely because the data is not collected for advertising.
  • ad-platform destinations — Meta, TikTok, Snap, Google. These send identifying data to a third party for advertising. That needs consent where consent law applies, and suppressing only the browser pixel while the server-side Conversions API keeps firing is theatre: the server leg carries more identifying data (hashed email, phone, IP) to the same companies.

allow?/2 answers the first. destinations_allowed?/1 answers the second. They are separate because answering them together is how a site ends up either over-collecting or throwing away its own analytics for no legal gain.

The EEA, the UK and Switzerland. Not everywhere: gating a visitor in Egypt or the Gulf costs real attribution data and buys nothing, because ePrivacy consent rules do not reach them. required?/1 is the list.

Global Privacy Control

Sec-GPC: 1 is honoured by default and is checked server-side, from the header. It is a legally binding opt-out under CCPA/CPRA and a growing set of US state laws — unlike DNT, which never had legal force, was ignored, and was removed from Safari. DNT is off by default and available as a courtesy.

Checked server-side for two reasons: the client cannot be trusted to check a signal about itself, and an ad-blocker may well have removed the script that would have.

It ships off

consent_gate: [enabled: true] turns the geographic gate on. The default is off, because switching it on before a banner exists silently stops tracking for those visitors, and the honest default is current behaviour until the decision is deliberate.

Summary

Types

A visitor's signals, as the plug layer extracts them.

Functions

May pixelex record a first-party event for this visitor?

Whether tracking is permitted, as a plain boolean.

May pixelex forward this event to third-party ad platforms?

The countries where a consent decision is legally required.

Should a banner be shown? Only when a decision is required and none has been made. nil and "denied" both block; opt-in, not opt-out.

Is a consent decision required for this visitor's country?

Types

signals()

@type signals() :: %{
  optional(:country) => String.t() | nil,
  optional(:decision) => String.t() | nil,
  optional(:gpc) => String.t() | nil,
  optional(:dnt) => String.t() | nil
}

A visitor's signals, as the plug layer extracts them.

  • :country — ISO-3166-1 alpha-2, from geo lookup or a CDN header
  • :decision"granted" / "denied" / nil, from the host's banner
  • :gpc — the Sec-GPC header value
  • :dnt — the DNT header value

Functions

allow?(signals)

@spec allow?(signals()) :: true | {:denied, :gpc | :dnt | :no_consent}

May pixelex record a first-party event for this visitor?

Returns true, or {:denied, reason} so the caller can report why nothing was recorded. A silent false here is indistinguishable from a bug.

allowed?(signals)

@spec allowed?(signals()) :: boolean()

Whether tracking is permitted, as a plain boolean.

For call sites that only branch and have nowhere useful to put the reason.

destinations_allowed?(signals)

@spec destinations_allowed?(signals()) :: true | {:denied, atom()}

May pixelex forward this event to third-party ad platforms?

Strictly narrower than allow?/1: a first-party pageview can be lawful where a Conversions API call is not.

gated_countries()

@spec gated_countries() :: [String.t()]

The countries where a consent decision is legally required.

prompt?(country, decision)

@spec prompt?(String.t() | nil, String.t() | nil) :: boolean()

Should a banner be shown? Only when a decision is required and none has been made. nil and "denied" both block; opt-in, not opt-out.

required?(country)

@spec required?(String.t() | nil) :: boolean()

Is a consent decision required for this visitor's country?