Skip to content
YuvaDocs

Consent features

8 min read

By default Yuva Analytics cannot recognise a visitor from one day to the next (Analytics and privacy). Consent features change that for visitors who agreed: with their yes, your site or app keeps a random id on their device, and you get returning visitors, retention, funnels that run over several days and, for signed-in users, their recent visits on their contact page. Everyone who did not agree is counted exactly as before.

This builds on Analytics and its consent mode. You bring your own consent tool; Yuva never shows a banner.

With consent to persistent What you get
A random id on the device Made by the script or app, kept for at most 13 months from when it was made, and sent with every event. The server stores only a hash of it under a secret of the property, so the same id means nothing on another property.
Visitors counted once A visitor counts once over a range, a week or a month instead of once a day.
New and returning The New and returning card on the overview, and the visitor_type dimension: new on the day the id was first seen, returning after it. Empty for everyone else.
Retention The Retention tab, and GET /v1/analytics/properties/{propertyId}/retention: cohorts by day, week or month and how many of each came back.
Funnels over days A funnel’s consented_visitors follows these visitors across visits and days, each counted once.
With identify as well The contact page’s Recent visits, and GET /v1/contacts/{contactId}/visits.

Only consenting visitors are in these views. A property that does not use the feature has no retention cohorts.

  • Nothing is stored on the device. The only exception is a visitor’s own opt-out.
  • No id is made, sent or read, and every visitor is counted by the hash of the day, as in the default mode.
  • A visitor is never joined to a contact or a conversation: the contact page lists no visits.
  • A browser that sends Do Not Track or Global Privacy Control gets no id, whatever the consent tool says.
  • A token sent without consent, or with a forged, expired or already used signature, is ignored.

Turn it on for a property

In the panel, open the website’s Settings → Tracking → Consent features. An owner or admin sets two switches (they are off and disabled for children’s properties); through the API they are the property’s consent_features:

Switch consent_features Does
Persistent id persistent Lets visitors who agree get the 13-month id.
Link signed-in users identify Lets the id be linked to a contact. Needs Persistent id and a website linked to an inbox, whose identity secret signs the tokens.

Or with an analytics:manage key:

curl -X PATCH -H "Authorization: Bearer $YUVA_API_KEY" -H "Content-Type: application/json" \
  https://support.example.com/v1/analytics/properties/$PROPERTY \
  -d '{"consent_features": ["persistent", "identify"]}'

Without the setting, persistent and identify are ignored and visitors are counted in the default mode.

Children’s properties cannot use them. A property with Audience: Children refuses consent_features (400): a child’s device is never given an id, and no child is linked to a contact.

Tell your visitors. Add to your privacy notice that a consenting visitor gets an id on their device for up to 13 months, what it is for and how they withdraw (withdrawal).

The script keeps no answer, so call it on every page load with the visitor’s current choice, and pass persistent: true on every call while the visitor agrees to it: any consent call without it, including consent({ analytics: true }), withdraws a stored id.

window.yuva.consent({ analytics: true, persistent: true })
window.yuva.consent(false)

consent({ analytics: true }) counts the visitor without an id. Adding persistent: true makes the id and keeps it in the browser’s local storage. consent(false), a call without persistent: true and an opt-out stop the id: it is deleted from the device and the server forgets its link to a contact and its retention days, even when the browser sends Do Not Track. Before the script has loaded, queue the call: ;(window.yuvaq = window.yuvaq || []).push(["consent", { analytics: true, persistent: true }]).

Script tag. The usual /yuva-analytics.js leaves the consent features out to stay small. To use them, load /yuva-analytics-consent.js (under 2 KB gzip) from the same place instead, with the same attributes, and set data-consent="required" as in consent mode. window.yuva then has identify and persistentId too. With useyuva/analytics, React and Nuxt the calls are there already.

Recipes

These are examples for the consent tools below, using the callbacks their documentation names. Check your tool’s own documentation, because names and categories change; what matters is that consent(...) is called with the visitor’s choice every time a page loads and every time the choice changes.

Any tool. Write one function and call it from whatever your tool calls on load and on change:

function syncYuva(analytics, persistent) {
  if (analytics) window.yuva.consent({ analytics: true, persistent })
  else window.yuva.consent(false)
}

Cookiebot. Cookiebot fires CookiebotOnConsentReady on every page load and when the choice changes; the statistics category is Cookiebot.consent.statistics.

window.addEventListener("CookiebotOnConsentReady", () => {
  const ok = Cookiebot.consent.statistics
  ok ? window.yuva.consent({ analytics: true, persistent: true }) : window.yuva.consent(false)
})

OneTrust. OptanonWrapper runs on load and after each change, and OnetrustActiveGroups lists the accepted category ids, C0002 being Performance:

function OptanonWrapper() {
  const ok = window.OnetrustActiveGroups.includes("C0002")
  ok ? window.yuva.consent({ analytics: true, persistent: true }) : window.yuva.consent(false)
}

Klaro. Add a service to the Klaro config; its callback gets the choice on load and on change:

services: [{
  name: "yuva",
  purposes: ["analytics"],
  callback: (consent) => {
    consent ? window.yuva.consent({ analytics: true, persistent: true }) : window.yuva.consent(false)
  },
}]

CookieYes. The cookieyes_consent_update event on document carries the accepted categories in event.detail.accepted when the choice changes, and getCkyConsent() answers on load:

const apply = (ok) =>
  ok ? window.yuva.consent({ analytics: true, persistent: true }) : window.yuva.consent(false)

document.addEventListener("cookieyes_consent_update", (event) => {
  apply(event.detail.accepted.includes("analytics"))
})
apply(getCkyConsent().categories.analytics)

If your tool has a separate switch for the id, pass its answer as persistent instead of true.

For signed-in users on a property that offers identify, hand the script a token your backend signs, as for the widget: the same inbox, the same secret and the same claims, with exp at most 10 minutes ahead and a jti that is used once. After the visitor signs in:

const { token } = await fetch("/yuva-identity").then((r) => r.json())
window.yuva.identify(token)

/yuva-identity is your own endpoint, the one the identity guide describes. Yuva checks the token as it checks one for a chat session, finds or makes the contact for its sub, and links the id and its visits to it. The visits show on the contact page and at GET /v1/contacts/{contactId}/visits (scopes contacts:read and analytics:read), for as long as the detailed records are kept. Call identify again after every sign-in; a token cannot be used twice. It does nothing for a visitor who has not agreed to both analytics and persistent.

Deleting a contact deletes the visits linked to it; merging two contacts moves them.

Withdrawal and erasure

A visitor who withdraws in your consent tool: your callback calls consent(false). The id leaves the device, and the server deletes the id’s link to a contact and its retention days. A withdrawal is taken even when the browser sends Do Not Track.

A person who asks you to erase what you hold about them: delete everything stored under their id with a key that has analytics:manage:

curl -X DELETE -H "Authorization: Bearer $YUVA_API_KEY" \
  https://support.example.com/v1/analytics/properties/$PROPERTY/visitors/$PERSISTENT_ID

It deletes the id’s raw visits and events, its retention days and its link to a contact, rolls the finished days up again without them, and answers 204 whether or not anything was stored. The id is 16 to 64 letters, digits, - and _. The person’s browser knows it: window.yuva.persistentId() returns it, so a “Delete my data” button on your site can send it to your backend, which makes the call with your key. Yuva stores only a hash of the id, so you cannot find it from a name or an e-mail address; to erase a signed-in user’s contact as well, see Delete a user.

Server events

A backend that reports events for a visitor who agreed can name them. Send the id the browser holds as persistent_id, with consent: true, to POST /v1/analytics/properties/{propertyId}/events:

{
  "events": [{
    "name": "Signup",
    "url": "https://www.example.com/signup",
    "persistent_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "consent": true
  }]
}

The event then belongs to the visitor’s days, as the browser’s own events do. With identity_token as well (on a property that offers identify) it identifies the visitor. Without consent: true the id is ignored. The other fields are in Server events.

Apps

The Swift and Kotlin libraries have the same calls as the script. Your own consent screen decides when to ask. The id lives in the app’s private storage for at most 13 months. As on the web, pass persistent: true on every consent call while the user agrees: a call without it withdraws the id.

YuvaAnalytics.consent(true, persistent: true)
YuvaAnalytics.identify(token)
let id = YuvaAnalytics.persistentId
YuvaAnalytics.consent(false)
YuvaAnalytics.consent(true, persistent = true)
YuvaAnalytics.identify(token)
val id = YuvaAnalytics.persistentId
YuvaAnalytics.consent(false)

identify and a withdrawal are sent once and are never queued. persistentId is nil or null when the user has no id. Call consent on every launch, as in Consent and opt-out.

Answers for the stores

These apply when persistent is on and the user agreed; without it, the answers on the apps page stand. The id is random, made by the library, not the advertising id, and it is shared with no one.

App Store. The Swift library’s privacy manifest declares Device ID (collected for analytics, not used for tracking), so Xcode’s privacy report already lists it. In App Store Connect answer:

Data type Linked to the user Used for tracking Purpose
Identifiers → Device ID No, unless your app calls identify No Analytics
Identifiers → User ID, only if you call identify Yes No Analytics

Yuva combines the id with no data from other companies’ apps or sites, so it is not tracking in Apple’s sense; answer No to tracking unless your own use says otherwise.

Google Play. In Data safety, still without AD_ID:

Data type Collected Shared Notes
Device or other IDs Yes No The random id, optional (the user chooses). Purpose: Analytics.
Personal info → User IDs, only if you call identify Yes No Optional. Purpose: Analytics.

Mark both as optional in the form, because the user can decline, and add that data can be deleted on request.

Reference

Call Does
PATCH /v1/analytics/properties/{propertyId} Sets consent_features. Scope analytics:manage.
GET /v1/analytics/properties/{propertyId}/retention Cohorts of consenting visitors (grain: day, week, month; from, to). Scope analytics:read.
DELETE /v1/analytics/properties/{propertyId}/visitors/{persistentId} Erases everything stored under an id. Scope analytics:manage.
GET /v1/contacts/{contactId}/visits A linked contact’s recent visits.
POST /v1/analytics/properties/{propertyId}/events Server events, with persistent_id, consent and identity_token.

Retention cohorts go up to 30 days or 12 weeks or months and are rebuilt every hour. An id is forgotten 13 months after it was first seen. Per address and property, 20 consent changes a minute are taken.

Edit this page on GitHub →