{"id":1032,"date":"2026-05-09T13:00:00","date_gmt":"2026-05-09T18:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1032"},"modified":"2026-03-07T04:46:07","modified_gmt":"2026-03-07T09:46:07","slug":"onboarding-compliance","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/onboarding-compliance\/","title":{"rendered":"Onboarding Compliance: COPPA, GDPR, and Age Gating"},"content":{"rendered":"\n<p>Compliance requirements affect onboarding directly. If your app could be used by children, you need COPPA compliance. If you serve EU users, GDPR consent must be collected during onboarding. Age gating, parental consent, data processing agreements, and privacy notices all need to be integrated into the onboarding flow without destroying the user experience.<\/p>\n\n\n\n<p>For security practices, see <a href=\"https:\/\/tolinku.com\/blog\/fintech-deep-link-security\/\">Security Best Practices for Fintech Deep Links<\/a>. For privacy-safe measurement, see <a href=\"https:\/\/tolinku.com\/blog\/attribution-data-clean-rooms\/\">Attribution Data Clean Rooms: Privacy-Safe Measurement<\/a>. For general onboarding best practices alongside compliance, see <a href=\"https:\/\/tolinku.com\/blog\/onboarding-best-practices-2026\/\">Onboarding Best Practices for Mobile Apps in 2026<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Age Gating<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">When Age Gating Is Required<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Regulation<\/th>\n<th>Applies When<\/th>\n<th>Age Threshold<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>COPPA (US)<\/td>\n<td>App collects data from children<\/td>\n<td>Under 13<\/td>\n<\/tr>\n<tr>\n<td>GDPR (EU)<\/td>\n<td>App processes data of minors<\/td>\n<td>Under 16 (varies by member state, 13-16)<\/td>\n<\/tr>\n<tr>\n<td>AADC (UK)<\/td>\n<td>App likely accessed by children<\/td>\n<td>Under 18<\/td>\n<\/tr>\n<tr>\n<td>LGPD (Brazil)<\/td>\n<td>App processes children&#39;s data<\/td>\n<td>Under 18 (or 12 for &quot;children&quot; vs. &quot;adolescents&quot;)<\/td>\n<\/tr>\n<tr>\n<td>PIPL (China)<\/td>\n<td>App processes minor&#39;s data<\/td>\n<td>Under 14<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Age Gate Implementation<\/h3>\n\n\n\n<p>A compliant age gate collects the date of birth without suggesting the &quot;correct&quot; answer:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">function AgeGate({ onVerified }) {\n  const [birthDate, setBirthDate] = useState(null);\n\n  function handleSubmit() {\n    if (birthDate === null) return;\n\n    const age = calculateAge(birthDate);\n\n    if (age &lt; 13) {\n      \/\/ COPPA: Cannot collect data without parental consent\n      navigation.navigate(&#39;ParentalConsent&#39;);\n      return;\n    }\n\n    if (age &lt; 16) {\n      \/\/ GDPR: May need parental consent depending on jurisdiction\n      navigation.navigate(&#39;MinorOnboarding&#39;, { age });\n      return;\n    }\n\n    onVerified({ age, birthDate });\n  }\n\n  return (\n    &lt;Screen&gt;\n      &lt;Heading&gt;Enter your date of birth&lt;\/Heading&gt;\n      &lt;Text&gt;We need this to provide the right experience for you.&lt;\/Text&gt;\n\n      &lt;DatePicker\n        value={birthDate}\n        onChange={setBirthDate}\n        maximumDate={new Date()} \/\/ Cannot be in the future\n        \/\/ Do NOT set a minimum date that hints at the required age\n      \/&gt;\n\n      &lt;Button onPress={handleSubmit} disabled={birthDate === null}&gt;\n        Continue\n      &lt;\/Button&gt;\n    &lt;\/Screen&gt;\n  );\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Anti-Gaming Measures<\/h3>\n\n\n\n<p>Prevent users from lying about their age:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">async function handleAgeVerification(userId, birthDate) {\n  \/\/ Store the attempt\n  await logAgeVerification(userId, birthDate);\n\n  \/\/ Check for repeated attempts (user going back and changing DOB)\n  const attempts = await getAgeVerificationAttempts(userId);\n\n  if (attempts.length &gt; 1) {\n    \/\/ User tried to change their age \u2014 use the FIRST attempt\n    \/\/ This prevents children from going back and entering an older date\n    birthDate = attempts[0].birthDate;\n  }\n\n  \/\/ Persist permanently (cannot be changed in settings)\n  await setUserBirthDate(userId, birthDate, { locked: true });\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">COPPA Compliance<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">What COPPA Requires<\/h3>\n\n\n\n<p>For users under 13 in the United States:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Verifiable parental consent<\/strong> before collecting, using, or disclosing personal information<\/li>\n<li><strong>Privacy notice<\/strong> specifically for parents<\/li>\n<li><strong>Limited data collection<\/strong> (only what&#39;s necessary)<\/li>\n<li><strong>Parental access<\/strong> to the child&#39;s information<\/li>\n<li><strong>Ability to delete<\/strong> the child&#39;s data<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Parental Consent Flow<\/h3>\n\n\n\n<pre><code class=\"language-javascript\">function ParentalConsentFlow({ childAge }) {\n  const [step, setStep] = useState(&#39;notice&#39;);\n\n  return (\n    &lt;View&gt;\n      {step === &#39;notice&#39; &amp;&amp; (\n        &lt;ParentalNotice\n          onContinue={() =&gt; setStep(&#39;verification&#39;)}\n        &gt;\n          &lt;Text&gt;\n            Because you are under 13, a parent or guardian must\n            give permission for you to use [App].\n          &lt;\/Text&gt;\n          &lt;Text&gt;\n            We will collect your parent&#39;s email to send a consent request.\n          &lt;\/Text&gt;\n        &lt;\/ParentalNotice&gt;\n      )}\n\n      {step === &#39;verification&#39; &amp;&amp; (\n        &lt;ParentalVerification\n          onSubmit={async (parentEmail) =&gt; {\n            await sendParentalConsentEmail(parentEmail);\n            setStep(&#39;waiting&#39;);\n          }}\n        \/&gt;\n      )}\n\n      {step === &#39;waiting&#39; &amp;&amp; (\n        &lt;WaitingScreen&gt;\n          &lt;Text&gt;\n            We sent a consent request to your parent&#39;s email.\n            You can use [App] once they approve.\n          &lt;\/Text&gt;\n        &lt;\/WaitingScreen&gt;\n      )}\n    &lt;\/View&gt;\n  );\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">COPPA-Compliant Data Collection<\/h3>\n\n\n\n<pre><code class=\"language-javascript\">const COPPA_ALLOWED_DATA = {\n  \/\/ Can collect without parental consent (for internal use only)\n  persistent_identifier: true, \/\/ For app functionality\n  \/\/ Requires parental consent\n  name: &#39;requires_consent&#39;,\n  email: &#39;requires_consent&#39;,\n  location: &#39;requires_consent&#39;,\n  photos: &#39;requires_consent&#39;,\n  \/\/ Never collect from children\n  behavioral_advertising_data: &#39;prohibited&#39;,\n};\n\nfunction getOnboardingFieldsForAge(age) {\n  if (age &lt; 13) {\n    \/\/ Minimal onboarding, no personal data collection\n    return {\n      fields: [&#39;display_name&#39;], \/\/ Pseudonymous\n      skip: [&#39;email&#39;, &#39;phone&#39;, &#39;location&#39;, &#39;avatar_upload&#39;],\n      requireParentalConsent: true,\n    };\n  }\n\n  return { fields: &#39;all&#39;, skip: [], requireParentalConsent: false };\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">GDPR Consent in Onboarding<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Consent Requirements<\/h3>\n\n\n\n<p>GDPR requires that consent be:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Freely given<\/strong> (not bundled with account creation)<\/li>\n<li><strong>Specific<\/strong> (separate consents for different purposes)<\/li>\n<li><strong>Informed<\/strong> (clear language about what happens with the data)<\/li>\n<li><strong>Unambiguous<\/strong> (affirmative action, no pre-checked boxes)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Consent Collection<\/h3>\n\n\n\n<pre><code class=\"language-javascript\">function GDPRConsentScreen({ onComplete }) {\n  const [consents, setConsents] = useState({\n    terms: false, \/\/ Required for service\n    privacy: false, \/\/ Required for service\n    analytics: false, \/\/ Optional\n    marketing_email: false, \/\/ Optional\n    marketing_push: false, \/\/ Optional\n    thirdPartySharing: false, \/\/ Optional\n  });\n\n  const requiredConsents = [&#39;terms&#39;, &#39;privacy&#39;];\n  const canContinue = requiredConsents.every(key =&gt; consents[key]);\n\n  return (\n    &lt;Screen&gt;\n      &lt;Heading&gt;Your Privacy Choices&lt;\/Heading&gt;\n\n      {\/* Required consents *\/}\n      &lt;Section title=&quot;Required&quot;&gt;\n        &lt;ConsentCheckbox\n          label=&quot;I accept the Terms of Service&quot;\n          link=&quot;\/terms&quot;\n          checked={consents.terms}\n          onChange={(v) =&gt; setConsents({ ...consents, terms: v })}\n        \/&gt;\n\n        &lt;ConsentCheckbox\n          label=&quot;I accept the Privacy Policy&quot;\n          link=&quot;\/privacy&quot;\n          checked={consents.privacy}\n          onChange={(v) =&gt; setConsents({ ...consents, privacy: v })}\n        \/&gt;\n      &lt;\/Section&gt;\n\n      {\/* Optional consents *\/}\n      &lt;Section title=&quot;Optional&quot;&gt;\n        &lt;ConsentCheckbox\n          label=&quot;Help improve [App] with usage analytics&quot;\n          description=&quot;We collect anonymous usage data to improve the app experience.&quot;\n          checked={consents.analytics}\n          onChange={(v) =&gt; setConsents({ ...consents, analytics: v })}\n        \/&gt;\n\n        &lt;ConsentCheckbox\n          label=&quot;Receive marketing emails&quot;\n          description=&quot;Product updates, tips, and offers. Unsubscribe anytime.&quot;\n          checked={consents.marketing_email}\n          onChange={(v) =&gt; setConsents({ ...consents, marketing_email: v })}\n        \/&gt;\n\n        &lt;ConsentCheckbox\n          label=&quot;Receive push notifications for offers&quot;\n          checked={consents.marketing_push}\n          onChange={(v) =&gt; setConsents({ ...consents, marketing_push: v })}\n        \/&gt;\n      &lt;\/Section&gt;\n\n      &lt;Button\n        onPress={() =&gt; {\n          saveConsents(consents);\n          onComplete(consents);\n        }}\n        disabled={canContinue === false}\n      &gt;\n        Continue\n      &lt;\/Button&gt;\n    &lt;\/Screen&gt;\n  );\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Storing Consent Records<\/h3>\n\n\n\n<p>GDPR requires you to prove consent was given:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">async function saveConsents(userId, consents) {\n  const record = {\n    userId,\n    consents,\n    timestamp: new Date().toISOString(),\n    appVersion: getAppVersion(),\n    consentVersion: &#39;2.1&#39;, \/\/ Version of consent text\n    ipAddress: getClientIP(), \/\/ For audit trail\n    method: &#39;onboarding_screen&#39;,\n  };\n\n  await consentLog.create(record);\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Deep Links and Compliance<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Age Gate Bypass Prevention<\/h3>\n\n\n\n<p>Deep links must not bypass age gates or consent flows:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">function handleDeepLink(url) {\n  const user = getCurrentUser();\n\n  \/\/ Check if age gate is required\n  if (user.ageVerified === false) {\n    pendingDeepLink.save(url);\n    navigation.navigate(&#39;AgeGate&#39;);\n    return;\n  }\n\n  \/\/ Check if GDPR consent is collected\n  if (user.region === &#39;EU&#39; &amp;&amp; user.gdprConsentCollected === false) {\n    pendingDeepLink.save(url);\n    navigation.navigate(&#39;GDPRConsent&#39;);\n    return;\n  }\n\n  processDeepLink(url);\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Consent-Aware Deep Link Parameters<\/h3>\n\n\n\n<p>GDPR and privacy regulations affect how deep link data is handled for attribution. For more on this topic, see <a href=\"https:\/\/tolinku.com\/blog\/attribution-and-gdpr\/\">Attribution and GDPR<\/a> and <a href=\"https:\/\/tolinku.com\/blog\/deep-linking-and-privacy\/\">Deep Linking and Privacy<\/a>.<\/p>\n\n\n\n<p>Don&#39;t pass data in deep links that requires consent you haven&#39;t collected:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">function processDeepLinkParams(params, consents) {\n  const processedParams = {};\n\n  \/\/ Always process these (necessary for service)\n  processedParams.ref = params.ref;\n  processedParams.product = params.product;\n\n  \/\/ Only process if analytics consent given\n  if (consents.analytics) {\n    processedParams.utm_source = params.utm_source;\n    processedParams.utm_campaign = params.utm_campaign;\n  }\n\n  \/\/ Only store if marketing consent given\n  if (consents.marketing_email) {\n    processedParams.email = params.email;\n  }\n\n  return processedParams;\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">iOS App Tracking Transparency<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">ATT in Onboarding<\/h3>\n\n\n\n<p>If you track users across apps or websites (for advertising attribution), you must show the ATT prompt on iOS:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">async function handleATTConsent() {\n  const status = await requestTrackingPermission();\n\n  switch (status) {\n    case &#39;authorized&#39;:\n      \/\/ Full attribution tracking\n      enableFullAttribution();\n      break;\n\n    case &#39;denied&#39;:\n    case &#39;restricted&#39;:\n      \/\/ Limited attribution (SKAdNetwork only)\n      enableLimitedAttribution();\n      break;\n\n    case &#39;not-determined&#39;:\n      \/\/ User hasn&#39;t decided yet\n      break;\n  }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">When to Show ATT<\/h3>\n\n\n\n<p>Don&#39;t show ATT on the first screen. Wait until the user has experienced value:<\/p>\n\n\n\n<pre><code class=\"language-javascript\">function shouldShowATT(user) {\n  \/\/ Don&#39;t show before the user has completed basic onboarding\n  if (user.onboardingStep &lt; 3) return false;\n\n  \/\/ Don&#39;t show if already determined\n  if (user.attStatus !== &#39;not-determined&#39;) return false;\n\n  \/\/ Show after the user has experienced value\n  if (user.hasCompletedFirstAction) return true;\n\n  return false;\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Compliance Checklist for Onboarding<\/h2>\n\n\n\n<ul class=\"checklist wp-block-list\"><li><input type=\"checkbox\" disabled> Age gate implemented (does not hint at correct answer)<\/li><li><input type=\"checkbox\" disabled> Age verification attempts logged and locked<\/li><li><input type=\"checkbox\" disabled> Parental consent flow for users under 13 (COPPA)<\/li><li><input type=\"checkbox\" disabled> GDPR consent collected before data processing (EU users)<\/li><li><input type=\"checkbox\" disabled> Consent records stored with timestamps and versions<\/li><li><input type=\"checkbox\" disabled> Optional consents not pre-checked<\/li><li><input type=\"checkbox\" disabled> Deep links cannot bypass age gates or consent flows<\/li><li><input type=\"checkbox\" disabled> ATT prompt shown at appropriate time (iOS)<\/li><li><input type=\"checkbox\" disabled> Data collection minimized for minor users<\/li><li><input type=\"checkbox\" disabled> Privacy policy and terms accessible during onboarding<\/li><li><input type=\"checkbox\" disabled> Consent withdrawal mechanism available in settings<\/li><\/ul>\n\n\n\n<p>For deep linking features, see <a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku deep linking<\/a>. For onboarding use cases, see the <a href=\"https:\/\/tolinku.com\/docs\/use-cases\/onboarding\/\">onboarding documentation<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Handle regulatory compliance during onboarding. Implement age gates, COPPA requirements, GDPR consent, and parental verification in your app&#8217;s setup flow.<\/p>\n","protected":false},"author":2,"featured_media":1031,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Onboarding Compliance: COPPA, GDPR, and Age Gating","rank_math_description":"Handle regulatory compliance during onboarding. Implement age gates, COPPA requirements, GDPR consent, and parental verification in your app's setup flow.","rank_math_focus_keyword":"onboarding compliance","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-onboarding-compliance.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-onboarding-compliance.png","footnotes":""},"categories":[18],"tags":[253,129,252,20,128,69,27,36,93],"class_list":["post-1032","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-use-cases","tag-age-gating","tag-compliance","tag-coppa","tag-deep-linking","tag-gdpr","tag-mobile-development","tag-onboarding","tag-privacy","tag-security"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1032","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=1032"}],"version-history":[{"count":4,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1032\/revisions"}],"predecessor-version":[{"id":2825,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1032\/revisions\/2825"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1031"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1032"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1032"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1032"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}