Bangkok.Digital Free CRO Audit
// COMPLIANCE · 2025-12-15 · 13 min read

PDPA Thailand for Analytics: What You Actually Need

What's required, what's optional, and what's overkill under Thailand's Personal Data Protection Act for analytics, GA4, and on-site tracking. With sample consent strings we ship in production.

By Yunmin Shin · Published 2025-12-15 · Bangkok
This is engineering and CRO advice, not legal advice. We've worked through PDPA implementations with multiple Thai law firms and regulators have started enforcement seriously since 2023, but every business should have its DPO sign off before going live. Use this as the practitioner checklist, not the final word.

What PDPA actually says (the short version)

Thailand's Personal Data Protection Act B.E. 2562 (2019) became enforceable in June 2022. It's a GDPR-style framework with similar concepts but several Thailand-specific differences that catch foreign teams off-guard. For analytics specifically, four parts of the law matter:

The PDPC (Personal Data Protection Committee) issues subordinate regulations and has been actively publishing guidance since 2023. Fines have been levied. We've seen administrative fines reach ฿3 million per violation in published cases. The era of "PDPA isn't really enforced in Thailand" is over.

Required: the absolute minimum for an analytics-running site

  1. A privacy notice reachable from every page that names the data controller, the categories of personal data collected (including IP, cookies, device IDs), the purposes, the lawful basis, retention periods, and the data subjects' rights. Bilingual TH/EN if you serve both.
  2. Consent capture for non-essential cookies, including analytics. GA4's default cookies (_ga, _ga_XXXXX) are non-essential under PDPA — this is the ruling we've seen most consistently. They require opt-in consent.
  3. A consent record per user that captures what they consented to, when, on what version of the privacy notice, and from which IP. Retain for at least the lifetime of the consent.
  4. An easy withdrawal mechanism — typically a link in the footer that re-opens the consent banner. PDPA explicitly says withdrawal must be as easy as giving consent.
  5. A data subject request endpoint — usually a contact email or form for users to request their data, deletion, or objection.
  6. A DPA with Google (and any other processor) — for GA4 this is automatic when you sign up, but the Thai entity needs to be the contracting party for cross-border transfer to be defensible.

That's it. Six items. Most Bangkok agency sites we audit miss two or three of them — typically the consent record and the easy withdrawal.

Optional: things that help but aren't strictly required

Overkill: what foreign agencies sometimes push that Thai law doesn't actually need

The consent banner that we ship

This is roughly what we deploy on a new client site. Bilingual, with explicit opt-in for analytics, consent record posted to a server-side endpoint backed by the Cloudflare Workers SSGTM we wrote about separately.

<!-- consent-banner.html — minimal, ships in production -->
<div id="pdpa-banner" hidden role="dialog" aria-labelledby="pdpa-title"
     class="fixed bottom-4 inset-x-4 max-w-2xl mx-auto rounded-2xl bg-stone-900 text-stone-100 p-6 shadow-2xl">
  <h2 id="pdpa-title" class="text-lg font-bold mb-2">เราใช้คุกกี้เพื่อปรับปรุงประสบการณ์ใช้งาน</h2>
  <p class="text-sm mb-3">
    เว็บไซต์นี้ใช้คุกกี้เพื่อการวิเคราะห์การใช้งาน (Google Analytics 4) เพื่อปรับปรุงเว็บไซต์ของเรา
    <br><span class="text-stone-400">We use cookies for analytics (GA4) to improve our website.</span>
  </p>
  <div class="flex flex-wrap gap-2">
    <button data-consent="all" class="px-4 py-2 rounded bg-orange-500 text-stone-900 font-bold">ยอมรับทั้งหมด · Accept all</button>
    <button data-consent="essential" class="px-4 py-2 rounded border border-stone-600">เฉพาะที่จำเป็น · Essential only</button>
    <a href="/privacy/" class="px-4 py-2 text-sm underline">นโยบายความเป็นส่วนตัว · Privacy notice</a>
  </div>
</div>
// consent.js — runs before any analytics
(function(){
  const KEY = 'pdpa_consent_v3';
  const stored = localStorage.getItem(KEY);
  if (stored) {
    applyConsent(JSON.parse(stored));
    return;
  }
  document.getElementById('pdpa-banner').hidden = false;

  document.querySelectorAll('[data-consent]').forEach(btn => {
    btn.addEventListener('click', () => {
      const choice = btn.dataset.consent;
      const record = {
        version: 'v3',
        choice: choice,                 // 'all' or 'essential'
        analytics: choice === 'all',
        marketing: choice === 'all',
        ts: new Date().toISOString(),
        notice_url: '/privacy/',
        ua: navigator.userAgent.slice(0,160),
      };
      localStorage.setItem(KEY, JSON.stringify(record));
      // Server-side log so we have a tamper-resistant record
      navigator.sendBeacon('/api/consent',
        new Blob([JSON.stringify(record)], {type: 'application/json'}));
      applyConsent(record);
      document.getElementById('pdpa-banner').hidden = true;
    });
  });

  function applyConsent(rec) {
    // GA4 consent mode v2
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('consent', 'update', {
      'analytics_storage': rec.analytics ? 'granted' : 'denied',
      'ad_storage': rec.marketing ? 'granted' : 'denied',
      'ad_user_data': rec.marketing ? 'granted' : 'denied',
      'ad_personalization': rec.marketing ? 'granted' : 'denied',
    });
    if (rec.analytics) {
      // Lazy-load GA4
      const s = document.createElement('script');
      s.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX';
      s.async = true;
      document.head.appendChild(s);
    }
  }
})();

The default-deny pre-script

Before any of the above runs, we set the default GA4 state to denied. This is what makes the implementation defensible: if a user closes the tab before clicking the banner, no personal data was sent. If they click "essential only," still nothing was sent. Only on explicit opt-in does GA4 fire.

<!-- top of <head>, before anything else -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
  'analytics_storage': 'denied',
  'ad_storage': 'denied',
  'ad_user_data': 'denied',
  'ad_personalization': 'denied',
  'wait_for_update': 1500
});
</script>

Sample consent record format

The consent record is the artifact you'll need if PDPC ever asks "prove this user opted in." We store it both client-side (so the banner doesn't re-prompt) and server-side (for audit). The server-side record:

{
  "consent_id": "01HZ3K7T6XFQXPCGT2H0C0J6E5",
  "user_pseudo_id_hash": "f3a9b7c1...",
  "ts": "2026-01-12T14:23:08+07:00",
  "ip_hashed": "sha256(ip + per_day_salt)",
  "country": "TH",
  "user_agent": "Mozilla/5.0 ...",
  "notice_version": "privacy-2026-01-01",
  "notice_url": "https://example.com/privacy/",
  "consents": {
    "analytics": true,
    "marketing": false,
    "personalization": false
  },
  "method": "banner_click",
  "withdrawal_url": "https://example.com/privacy/?withdraw"
}

Note the hashed IP. We don't store raw IPs — Section 24 cross-border transfer rules and minimization principles both push toward this. We rotate the salt daily so even hashed IPs aren't joinable across days unless you have the salt set.

Common implementation mistakes

1. Soft-pixel firing before consent. Facebook's pixel and TikTok's pixel both have "auto-load" defaults that fire as soon as the script loads. If your CMS includes them by default, they fire before the consent banner is shown. This is the most common compliance bug we find. Fix: gate every pixel load behind applyConsent(record).

2. Treating cookie consent as a once-and-done. When you change what cookies you set, you need to re-prompt for consent. We version the notice (v3 in the banner above) and re-prompt whenever the version changes. Most Bangkok sites set this once in 2022 and have never re-prompted despite adding TikTok and LINE pixels since.

3. Not honoring the "essential only" choice. Some implementations fire analytics anyway "because we anonymized it." That's not how consent works. If the user said no, no data goes to a third-party processor regardless of anonymization.

4. Cross-border transfer disclosure missing. GA4 sends data to Google servers, which under PDPA Section 28 is a cross-border transfer. Your privacy notice has to disclose this. We've reviewed dozens of Bangkok privacy notices and most don't mention it.

5. No consent-withdrawal endpoint. Easy mistake to skip; PDPA explicitly requires it. We add a ?withdraw query param that re-opens the banner with current state, plus an email contact for full data deletion requests.

How this interacts with the rest of our stack

The consent record from above feeds directly into the SSGTM Cloudflare Worker. Every event the Worker processes carries a consent_id field. The downstream BigQuery view filters out events whose consent record doesn't include analytics consent. The Markov attribution model only sees consenting users, which is technically a coverage limitation we document with clients (typical analytics consent rate in Thailand: 78-92% across our portfolio, much higher than EU-style sites).

For experimentation under sequential testing: experiment assignment is essential (it's how we serve the right page) and uses a different lawful basis (legitimate interest, narrowly framed). The logging of experiment outcomes for analysis is gated by analytics consent.

For checkout flows: don't put PDPA consent on the cart page. We covered this in the Thai checkout piece — front-loading consent tanks cart-to-checkout conversion. Banner on first visit, consent record persisted, PDPA notice link in footer, separate "I agree to the privacy notice" checkbox at order placement (which is a different lawful basis: contract performance for order data).

Data subject requests in practice

How often does this come up? In our portfolio of about ~20 active client sites, we field roughly 3-7 data subject requests per quarter total. Most are deletion requests from users who churned. We respond to every request within 30 days (PDPA's outer limit), document the response, and provide a deletion confirmation email. Of the requests we've received, fewer than 10% have been escalated or repeated — a generic "your data has been deleted from our analytics" reply satisfies almost everyone.

For deletion specifically: GA4 has a built-in data deletion endpoint, but it operates on user_id, not user_pseudo_id. Plan for both. We maintain a per-client deletion log (date, user identifier, scope of deletion) for our own audit trail.

Bottom line for Bangkok teams

If you take three things from this: (1) consent before fire, default deny, (2) keep a server-side consent record, (3) make withdrawal as easy as consent. The rest is execution detail. PDPA is not as onerous as GDPR but it is being enforced, and the implementation pattern above passes the legal reviews we've been through.

If you want us to audit your current consent setup against this checklist, email us. Free 30-minute review for any site under 5M monthly events. We do this work alongside Bluewich when implementation requires backend changes, alongside SEO Agency Bangkok for joint clients, and reference our partner SitPlay Media for the privacy-notice copy itself, which has to be readable by humans and not just defensible.

For the full operational view of how PDPA flows through our stack — ingestion, attribution, experimentation, retention — see the services page and recent case studies. Compliance work isn't billed separately on our retainers; it's a precondition.

Tags: pdpa thailand privacy consent compliance
// RELATED INSIGHTS
// ATTRIBUTION · 2026-04-18

Markov Attribution in BigQuery: A Working Example

Step-by-step Markov-chain attribution in BigQuery SQL.

// EXPERIMENTATION · 2026-03-25

Stop Peeking: Sequential Testing for Real Experimentation

Why fixed-horizon A/B tests inflate FPR by 5-15x.

// ANALYTICS · 2026-02-14

GA4 Server-Side on Cloudflare Workers

~฿8K/mo cost, latency profile, deployment scripts.

// THAI MARKET · 2026-01-22

Thai Checkout Patterns: PromptPay · LINE Pay · TrueMoney

Conversion patterns scraped across 200+ TH e-commerce sites.

PDPA audit in 30 minutes.

Free review of your current consent setup against the practitioner checklist. We'll tell you the gaps and rank them by risk.

Free CRO Audit Call +66 61 093 4014
💬 LINE

Yunmin Agency Network

Bluewich · SitPlay Media · SEO Agency Bangkok · Bangkok Digital

// WEEKLY THAI MARKET INSIGHTS

Get the data we scraped this week.

Rising keywords. SERP shifts. AI citation changes. Bangkok-market specific. No fluff, no sales — one email Tuesday morning.

No spam · Unsubscribe in one click

📱 WhatsApp · 💬 LINE · 📞 +66 61 093 4014

© 2026 · Operated by Yunmin Co., Ltd. · Thai Co. Reg. (pending) · 3rd Floor, 272 Than Thip 3 Alley, Phlabphla, Wang Thonglang, Bangkok 10310

Privacy · Terms · Atelier · umma@xx.gg