Consenti

GDPR Compliance Guide

Consenti is built from the ground up around GDPR requirements. All core design decisions follow the six lawfulness principles in Article 5.

ℹ️Compliance group: opt-in — covers GDPR (EU / EEA), UK GDPR, PDPA-TH (Thailand), and KVKK (Turkey) by default. PIPEDA (Canada), POPIA (South Africa), and APPI (Japan) auto-resolve to the more permissive general-privacy-consent group instead — see the Compliance Groups page for why — but their own regulation pages recommend opting into opt-in for stronger alignment. Use compliance: { type: 'opt-in' } in your ConsentiSetup config to activate the opt-in Compliance Group explicitly.

Official references

Key requirements and how Consenti meets them

RequirementImplementation
Freely givenNo content gating by default — the initial banner never blocks page content or trackers, and overlay opacity is 0 unless explicitly configured. Content gating (blocking the page behind an overlay until a choice is made) is available as an advanced, opt-in operator setting for sites that need it — use deliberately, since forced gating can itself undermine "freely given" consent under GDPR.
SpecificPer-cookie granularity — each cookie has its own consent entry in consentJson
InformedhtmlText on categories, legitimateInterestDescription for LI basis
UnambiguousNo pre-ticked boxes; all non-mandatory cookies default to 'denied'
Easy to withdrawThe preference modal (widget.showModal()) lets a visitor change or revoke any category with the same number of clicks as granting — that UI is the Art. 7(3) implementation. The erase API below is controller tooling for full-account deletion, not the visitor-facing withdrawal path.
Records keptconsent_history table (retained per purgeAfterDays, deleted on erasure — see below); audit_logs for admin actions (append-only, never deleted by Consenti)

Right to erasure (Article 17)

http
DELETE /consenti/api/v1/consent/:visitorId

This deletes:

  • All entries in consent_records for the visitor ID
  • All entries in consent_history for the visitor ID
  • The visitors record (IP hash, UA hash, geolocation)

The visitor ID itself is a random UUID and contains no PII.

For how to give visitors a self-service "Forget me" button in the widget itself, the events both sides fire, and how this fits together as one flow, see the Right to Erasure guide.

Data minimisation (Article 5(1)(c))

  • IP addresses are stored as SHA-256 hashes only — the raw IP is never persisted
  • User agents are stored as SHA-256 hashes only
  • Consent records contain only: visitor UUID, profile ID, consent choices, timestamp, source

Consent records as evidence

Every consent record includes:

  • profileId — the exact profile the user consented under. A new id is minted on every profile edit (the previous one is kept, archived, as history), so this alone identifies which cookie policy version was in effect — no separate version counter needed
  • createdAt / updatedAt — exact ISO 8601 timestamps
  • source'banner', 'api', or 'import'
  • consent_history — a change-log of every update to the record; retained per purgeAfterDays and deleted along with the record on erasure (distinct from audit_logs, which is append-only and never deleted by Consenti)

Legitimate Interest (Article 6(1)(f))

Configure a category with legalBasis: 'legitimate_interest' in your profile — legal basis is set once per category, and every parameter listed in cookies inherits it:

json
{
  "preferenceModal": {
    "categories": {
      "marketing": {
        "heading": "Marketing",
        "htmlText": "Relevant ads based on your activity.",
        "legalBasis": "legitimate_interest",
        "legitimateInterestDescription": "We show relevant ads under legitimate interest.",
        "cookies": ["ad_personalization"]
      }
    }
  }
}

Status values for LI parameters:

  • "granted" — user did not object
  • "objected" — user exercised their right to object (GDPR Art. 21)
ℹ️The 'objected' status is only valid for parameters whose category has legalBasis: 'legitimate_interest'. The server rejects 'objected' for standard consent-basis parameters.

Re-consent when the profile changes

Profiles have no in-place version counter — every edit mints a brand-new Profile.id and archives the previous one as history. The widget's verify endpoint compares the consent's profileId against the compliance group's currently active profile id and returns { valid: false, reasons: ['profile_changed'], currentProfileId, consentProfileId } when they differ, triggering the banner to show again.

Banner requirements

⚠️Under GDPR, the "Reject Optional" option must be as prominent as "Accept All". Do not use dark patterns such as grey-out reject buttons or hiding the reject option behind extra clicks. Consenti's default profile is compliant, but custom profiles are your responsibility.
  • Do not set overlayOpacity above 0 if users can't access site content without consenting
  • Always provide a way to withdraw consent (use BannerTrigger or widget.showModal() from a footer link)
  • Mandatory categories must be truly necessary — don't put analytics parameters in a legalBasis: 'mandatory' category