2023 年 6 月,法国数据保护机构 CNIL 对 Criteo 开出 4000 万欧元 罚单,理由是它无法证明被自己重定向的用户真的同意过。CNIL 并没说 Criteo 主观恶意,而是说 Criteo 拿不出一条能用的证据链——对任意一次具体的出价,它没有任何可审计的方式去证明:背后那个用户说过”可以”。
这正是 IAB TCF 存在要解决的问题。
TCF 属于那种罕见的 AdTech 协议——它长得像一个传输格式,本质却是一份法律合同。它把 GDPR 那条抽象义务——“只有在具备合法性基础时才能处理个人数据”——编码成一串 Base64,让上百家公司在 100ms 以内经 OpenRTB 把它传来传去,每一家都独立判断:眼前这个用户,是否同意了我接下来要做的那件事。
这篇是一份在线工程师对该协议的实操走查:CMP 在页面上做什么、一条 TC String 里到底打包了什么、OpenRTB 怎么携带它、你的服务端收到后必须做什么,以及 GPP 怎么把 TCF 包进一个更大的全球合规信封。 不是一篇法律科普——但每一节都落在一个真实的 bidder、ad server 或测量供应商为了别上 CNIL 下一份新闻稿,必须落地实现的东西上。
范围说明:在 App 广告 SDK 那篇里,我把端上的同意管理与合规一笔带过、说”协议基础另文展开”——这篇就是那篇。和 RTA 里”fail-open 是一个正当的商业选择”恰好相反,TCF 里的 fail-open 几乎永远是一桩正在酝酿的合规事故,下文 §6.4 会专门讲清这个对照。
TL;DR
- TCF 是一份标准化的同意合同,不是一个传输格式。它解决一个非常具体的 GDPR 问题:一次曝光会流经几十家公司,每一家都需要独立、实时地知道——这个用户是否同意了这家公司对其数据的使用。没有 TCF,每家自建一套;有了 TCF,大家共享一份 GVL(Global Vendor List) 和一条 TC String。
- TC String 是物理载体:一个 base64url 编码、按段(segment)切分的位域(bitfield)。Core 段装着 11 个 purpose 的 consent / 正当利益位,外加一张按 vendor ID 索引的位图,标明谁被授权。
- OpenRTB 用四个字段携带它,这就是全部的线级集成:
regs.ext.gdpr(2.6 里是regs.gdpr)、user.ext.consent(或user.consent)、regs.gpp、regs.gpp_sid。其余一切都是这四个字段的下游。 - GPP 不是 TCF 的继任者,而是一个信封:GPP 把 TCF(EU/UK)、US Privacy,以及越堆越高的美国各州法律,包进一条
gpp字符串,让全球 DSP 不必为每个司法辖区单独做一套传输。gpp_sid告诉你里面装了什么。 - 服务端的决策就是三步检查:解码 TC String → 核验你的 vendor ID 是否被授权 → 核验你需要的 purpose 是否被授权。第一步错了是合规事故,第二步错了是丢出价。
怎么读这篇(按角色取所需):赶时间的工程师,§3(TC String 解剖)+ §4.4 / §4.5(consent vs LI、publisher restrictions)+ §6(端到端集成)+ §8(生产坑) 是必读主干,足够你负责任地上线。§4.1–§4.3(11 个 purpose / feature 全枚举)当速查参考,第一遍可跳读、用到再回来查。§7(GPP)和 §9(可观测性) 在你公司走向全球或监管再加一部州法时再读不迟。
Table of contents
Open Table of contents
1. TCF 为什么存在:从 GDPR 到一个传输协议
2018 年 5 月 GDPR 生效时,AdTech 立刻发现自己处境尴尬。
一次广告曝光会触及几十家公司——媒体、SSP、ad exchange、DSP、DMP、CDP、测量供应商、验证供应商、CMP——其中任何一家都可能在链路上”处理个人数据”。GDPR 要求每一次处理行为都建立在一个合法性基础之上,而在广告里真正用得上的两个选项是同意(consent,Art. 6(1)(a))和正当利益(legitimate interest,Art. 6(1)(f))。
这立刻带来三个工程问题:
- 逐 vendor 的粒度:一次曝光触及 30 家公司,每一家都要知道用户是否专门对它说了”可以”。
- 逐 purpose 的粒度:不同公司要数据干不同的事——归因、重定向、受众测量、反作弊。同一个用户可能对 A 的目的说”可以”、对 C 的目的说”不行”。
- 100ms 的线级预算:传输靠 OpenRTB,没有空间给每条 bid request 附上 30 份独立的同意声明。
IAB 的答案就是 TCF:一份标准化的统一 vendor 注册表,加一张按 vendor ID 索引的位图,让这 30 家公司共享同一份序列化的同意状态。
整套框架立在三块基石上:
| 支柱 | 是什么 | 由谁维护 |
|---|---|---|
| GVL(Global Vendor List) | 一个 JSON 文件,列出每一家 TCF 注册 vendor,各有唯一 ID,并声明它需要哪些 purpose / feature | IAB Europe,按月版本化 |
| TC String | 一个 base64url 编码的位域,编码这个用户授权了哪些 vendor 和 purpose | 用户交互后由 CMP 生成 |
| CMP(Consent Management Platform) | 一个页面上的 SDK,弹出同意 UI、收集选择、生成并持久化 TC String,再通过标准 API 暴露它 | 媒体选定的第三方——OneTrust、Sourcepoint、Didomi、Cookiebot 等——或自建 |
最小心智模型:CMP 是生产者,TC String 是产物,GVL 是 schema,每一个下游 vendor 都是消费者。
2. 四个角色与数据流
生产者 → 产物 → 消费者的流向:CMP 产出 TC String,页面通过
__tcfapi 暴露它,每个下游 vendor 各自独立解码、各自判断自己是否被授权行动。最终判断永远是 vendor AND purpose——把这个 AND 搞错,是最常见的合规失败(见 §6.3)。
四个角色:
- Publisher(媒体):站点或 App 的运营方。在 GDPR 下法律身份是数据控制者(data controller),负责部署 CMP、确保正确的告知到位。
- CMP:跑在媒体侧的一段 JavaScript SDK(或 App 库),弹出同意 UI、收集用户选择、生成 TC String,并把它持久化到第一方 cookie / localStorage / App 存储。每个 CMP 都要向 IAB Europe 注册,拿到一个数字 CMP ID。
- Vendor(供应商):DSP、SSP、测量方、验证方、ad server、DMP——链路上任何处理用户数据的角色。每一个在 GVL 里都有唯一的 vendor ID。
- User(用户):协议的主体。每一个同意位都是相对于单个用户而言的。
值得点一句:媒体本身也是一个特殊的 vendor。TC String 里有一个专门的段叫 Publisher TC,装的是用户对媒体自身数据用途的同意(独立于第三方 vendor 同意)。它是服务端代码最常忘记其存在的那个段。
3. TC String:协议层的解剖
TC String 就是那个传输格式。一条真实的长这样:
CQi298AQi298AEsACCENCbFoAP_gA...AA.ILCJB_C7NbXFmybZ36...
各段用 . 拼接。哪些必需、哪些可选随版本变过;截至 2025 年 4 月的 v2.3 规范更新,规则是:
| 段 | 必需? | 内容 |
|---|---|---|
| Core String | 必需 | 元数据 + purpose 的 consent / LI + vendor 的 consent / LI + publisher restrictions |
| Disclosed Vendors | v2.3 起必需 | CMP 实际向用户披露过的那组 vendor(解决了 special-purpose-only vendor 长期以来的一处歧义) |
| Publisher TC | 可选 | 媒体自身的 purpose 同意,含任何自定义 purpose |
TC String 是各段用字面量
. 拼接而成。Core 永远在;Disclosed Vendors 在 v2.3 变成强制;Publisher TC 可选,也是服务端代码最常忘记去读的那个段。
如果你的服务端代码写于 2025 年 4 月之前,单段字符串(只有 Core)仍能解码,但形式上已不完整。现代 CMP 发的是两段或三段的字符串。
3.1 VendorConsents:bitfield 与 range 的取舍
规范里工程上最有意思的细节,是 VendorConsents(以及与之对应的 VendorLegitimateInterests)怎么编码。段里有一个位 IsRangeEncoding,在两种布局之间切换:
- Bitfield 编码:一个长度为
MaxVendorId的扁平位数组。第i位置 1,表示 vendor IDi被授权。 - Range 编码:一个变长的区间列表,例如
[1–5, 7, 9–20, 50–100]。
哪个更短,完全取决于被授权集合有多稀疏:
| 场景 | 更短的编码 |
|---|---|
| 用户点了”全部接受” → 几乎每个 vendor ID = 1 | Bitfield(一长串 1) |
| 用户点了”全部拒绝” → 几乎每个 vendor ID = 0 | Bitfield(一长串 0) |
| 用户只对少数几家 vendor opt-in → 高度稀疏 | Range——而且优势很大 |
CMP 把两种都算一遍、取更小的那个。对单个用户来说差别可以忽略,但 cookie 有硬性大小上限(多数浏览器是每条 cookie 4KB),而 TCF 字符串远不是唯一在抢这点预算的东西。
3.2 解码一条真实的 TC String
下面是一条真实的生产 TC String(Core + Disclosed Vendors,两段):
CQi298AQi298AEsACCENCbFoAP_gAEPgAATAK8pB_C7NbWFiybZ3aLtEcAhHV9BjbsQwAAaJA2ABTDqWoJwHw0EYFAzQlKIKGQIAqiTBIQIkCACABUCgIIAFgSBMQEyQoDBKIoAggBMBAGFYAExiGIJAEQCIAKIUNEEAmBwJocIUWEDwywAABhKRUAQxEAAAAIagCYFcBAMAC8IEdAgMKYIIjEDAEGQKAlkbpCABojQYQhEtVAkAFiTyONgHDkZDgXUhEvoYsAlxm4oCCwiQfwuzW1xZsiwV-C7RDAIV1fQA27EMAAGiQNgAUw6lqCMA9tBEgRIkBCmCBkAAKIkQSAAKAgIAARAoQCAFQEATABMEKAwSADAIIAzAUBgUABIQgiCQBEAiACiFDVBQIkMCKDCFlhAsEkAICYSgVAEERAAAAiCABmBTAABAAriBPQQCECCCIxAwBBACAIZG5QgBaB0GMIRjVSIABQkcjDAFyxAQ8F1IQDoGLGJZNqKggBAA.ILCJB_C7NbXFmybZ36LtEcAhXV9BjbsQwAAaJA2ABTDqWoJwH20EaFEzQlKYKGQIAqiTBIQIsCAiABUChIIAVgSBMQEyQoDBKIsAggDMBQGFYAExiGIJAEQCIAKIUNUFAmRwJocIWWEDwywAgJhKRUAQxEAAACIagGYFcBAMAC-IE9BgMaYIIjEDAEGQKAlkbtCAFonQYwhGtVIkAFiTyONgXLkZDwXUhEvoYsYl124qCAE
把它贴进官方解码器 iabgpp.com/#/decode,会得到大致如下结果:
| 字段 | 值 |
|---|---|
version | 2 |
cmpId | 300(Sourcepoint) |
cmpVersion / consentScreen / consentLanguage | 2 / 2 / EN |
vendorListVersion | 155——你的服务端应当拉 GVL v155 才能正确解读这条字符串,见 §8.1 |
policyVersion | 5——v2.2 上线时是 4;随 GVL v3 一起升到 5。读它,别硬编码 |
isServiceSpecific | true |
publisherCountryCode | CY(塞浦路斯) |
purposeConsents | {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}——11 个 purpose 全部 opt-in |
purposeLegitimateInterests | {2, 7, 8, 9, 10, 11}——注意缺了 1、3、4、5、6:purpose 1 按定义只能走 consent;3/4/5/6 是 v2.2 砍掉 LI 选项的”画像四件套” |
specialFeatureOptins | {1}——opt-in 了精确地理位置,但没 opt-in 设备指纹 |
vendorConsents 数量 | 1,401 家 vendor |
vendorLegitimateInterests 数量 | 1,412 家 vendor |
一个教科书式的”全部接受”——1,400 家 vendor 的同意率,强烈暗示这个 CMP 的”全部接受”按钮在视觉上压过了”全部拒绝”按钮。v2.2 之后,这件事本身就是一项合规风险(见 §8)。
4. 目的、特征与合法性基础
按现行 IAB Europe TCF Policies, Appendix A,TCF v2.2/v2.3 定义了 11 个 Purpose、3 个 Special Purpose、3 个 Feature、2 个 Special Feature。vendor 在 GVL 里声明自己需要哪些,用户通过 CMP 决定。
4.1 11 个 Purpose(取自 Appendix A 原文)
| ID | 官方名称 | 一句话是什么 |
|---|---|---|
| 1 | Store and/or access information on a device | Cookie、localStorage、设备 ID。**唯一允许的合法性基础:Consent。**这是 Appendix A 里唯一明确钉死法律依据的 purpose——它直接对应 ePrivacy Directive Art. 5(3)。 |
| 2 | Use limited data to select advertising | 非个性化(上下文)选广告,外加 frequency capping |
| 3 | Create profiles for personalised advertising | 为广告定向建立逐用户画像 |
| 4 | Use profiles to select personalised advertising | 用画像去定向某个用户 |
| 5 | Create profiles to personalise content | 同 3,用于编辑内容 |
| 6 | Use profiles to select personalised content | 同 4,用于编辑内容 |
| 7 | Measure advertising performance | 归因、转化、可见性 |
| 8 | Measure content performance | 内容侧的同上 |
| 9 | Understand audiences through statistics or combinations of data from different sources | 受众测量、面板派生的人口统计 |
| 10 | Develop and improve services | 产品改进、ML 模型训练 |
| 11 | Use limited data to select content | v2.2 新增——purpose 2 在内容侧的上下文对应物 |
4.2 3 个 Special Purpose
special purpose 只能在**正当利益(LI)**下处理——用户无法拒绝。理由是:没有它们,平台要么坏掉,要么违反其他法律。
| ID | 官方名称 | 现实例子 |
|---|---|---|
| 1 | Ensure security, prevent and detect fraud, and fix errors | IVT 检测、反 bot、错误恢复 |
| 2 | Deliver and present advertising and content | 把广告字节从服务器搬到用户——用到 IP、UA、屏幕尺寸这些你压根跳不过的东西 |
| 3 | Save and communicate privacy choices | 元目的:持久化并传递 TCF 状态本身。CMP 和 vendor 用它来读写 TC String |
4.3 Feature 与 Special Feature
Feature(3 个)——vendor 声明它们是追求某个 purpose 时所用的手段,没有单独的用户勾选项,但要披露:
| ID | 官方名称 |
|---|---|
| 1 | Match and combine data from other data sources |
| 2 | Link different devices |
| 3 | Identify devices based on information transmitted automatically(经 IP / UA / TCP 信号的被动指纹) |
Special Feature(2 个)——需要用户显式 opt-in,而不只是泛泛的同意:
| ID | 官方名称 |
|---|---|
| 1 | Use precise geolocation data(精度低于 500m,或经纬度小数点后多于 2 位) |
| 2 | Actively scan device characteristics for identification(主动指纹——字体、插件、屏幕分辨率) |
Feature 3 与 Special Feature 2 的区别很关键。前者是”我们接收并使用自动到达的设备信息”——只需披露。后者是”我们主动探测设备的可识别特征”——需显式 opt-in。如果你的产品做任何形式的指纹识别,你必须清楚自己站在这条线的哪一侧,因为多数 CMP UI 把 special feature 折进了二级菜单,opt-in 率惨不忍睹。在欧盟把商业模式建在 Special Feature 2 上,基本是行不通的。
4.4 Consent vs Legitimate Interest:逐目的检查
每个 purpose 最多有两种可能的合法性基础(v2.2 关掉了好几个):
- Consent——用户显式 opt-in。位在
PurposesConsent里。 - Legitimate Interest——vendor 主张 LI,用户有权反对。位在
PurposesLITransparency里。
对单个 purpose 的正确服务端判断:
对我需要的每个 purpose P:
在 GVL 里查这个 vendor 为 P 声明的法律依据。
声明 consent → 检查 PurposesConsent[P] == 1
声明 LI → 检查 PurposesLITransparency[P] == 1
声明 flexible → 看默认值,再叠加任何
publisher restriction 的覆盖
未声明 → fail
**这是 TCF 里被实现得最离谱的一步。**常见的失败模式:
- vendor 在 GVL 条目里把某 purpose 声明为 LI,但服务端只检查
PurposesConsent。consent 位正确地为 0(用户从未显式同意),服务端把请求当成未授权,于是收入悄悄漏掉。 - vendor 把某 purpose 声明为 Consent,但服务端检查
PurposesLITransparency。LI 位默认可能是 1,服务端处理了它本无权处理的数据。这就是合规事故。
规则是:信 vendor 的 GVL 声明,别信你的直觉。
4.5 Publisher restrictions:覆盖机制
媒体有权在逐 purpose 层面覆盖一个 vendor 声明的法律依据。三种 restriction 类型:
| 类型 | 含义 |
|---|---|
| 0 | vendor 完全不得使用此 purpose(硬禁) |
| 1 | 此 purpose 上 vendor 必须依赖 consent,哪怕它的 GVL 声明了 LI |
| 2 | 此 purpose 上 vendor 必须依赖 LI,哪怕它的 GVL 声明了 consent |
restriction 住在 Publisher TC 段里(某些情况下在 Core 里)。**服务端决策顺序很重要:先应用 publisher restrictions,再检查那个对的位。**跳过这一步,是生产代码即便在开发者自以为懂 TCF 时仍会脱离合规的最常见原因。
5. Scope:如今只剩一种合法模式
历史上规范通过 Core String 里的 IsServiceSpecific 位定义了两种 scope 模式:
| Scope | 含义 | 今日状态 |
|---|---|---|
Service-Specific(IsServiceSpecific = 1) | TC String 只对当前媒体 / 媒体组有效,存在媒体自己的域名下 | 唯一合法模式 |
Global(IsServiceSpecific = 0) | 经 consensu.org 共享域名在所有媒体间共享 TC String,配一套 Out-of-Band(OOB)披露机制 | 2021 年 9 月 1 日起废弃 |
现行技术规范说得毫不含糊:“This field must always have the value of 1. When a Vendor encounters a TC String with IsServiceSpecific = 0 then it is considered invalid.”
换句话说:IsServiceSpecific != 1 不是一个向后兼容问题,而是一条无效字符串。fail-closed,丢弃它。
global scope 为什么死了:它依赖一个 consensu.org 的第三方 cookie,而 Safari ITP 加上各浏览器更大范围的第三方 cookie 弃用,早在 IAB 正式宣判它之前就让它失效了。
一句话:服务端只认
IsServiceSpecific = 1。见到 0,当无效字符串处理、fail-closed——别把它当成需要兼容的旧格式。
6. 端到端集成:工程师的实操走查
6.1 客户端:__tcfapi 这套 CMP API
IAB 标准化了 CMP 的 JavaScript 接口面。每个 TCF v2 合规的 CMP 都必须暴露 window.__tcfapi:
window.__tcfapi('getTCData', 2, function(tcData, success) {
if (!success || !tcData) return;
if (tcData.cmpStatus !== 'loaded') return;
if (tcData.eventStatus !== 'tcloaded' &&
tcData.eventStatus !== 'useractioncomplete') return;
// tcData.tcString 就是我们要发往后端的那个传输格式。
// tcData.gdprApplies 告诉我们 GDPR 是否适用。
loadAdSDK(tcData.tcString, tcData.gdprApplies);
});
addEventListener 订阅同意变化——比如用户回到 CMP 二级菜单,把”全部接受”改成”全部拒绝”。收到 useractioncomplete 时,广告 SDK 应当停止任何刚刚失去合法性基础的数据采集:
window.__tcfapi('addEventListener', 2, function(tcData, success) {
if (!success) return;
if (tcData.eventStatus === 'useractioncomplete') {
revalidatePermissions(tcData);
}
});
TCData 对象上的核心字段:
| 字段 | 含义 |
|---|---|
tcString | 完整 TC String |
cmpId、cmpVersion | CMP 标识 |
gdprApplies | true / false / undefined |
eventStatus | tcloaded | cmpuishown | useractioncomplete |
cmpStatus | stub | loading | loaded | error |
listenerId | 供 removeEventListener 用 |
purpose.consents[1..11] | 逐 purpose 的 consent |
purpose.legitimateInterests[1..11] | 逐 purpose 的 LI |
vendor.consents[<vendorId>] | 你的 vendor ID 是否有 consent |
vendor.legitimateInterests[<vendorId>] | 你的 vendor ID 是否有 LI |
specialFeatureOptins[1..2] | special feature 的 opt-in |
publisher.restrictions | publisher 覆盖 |
CMP stub 陷阱:在完整 CMP 加载之前,__tcfapi 就已经存在——每个 CMP 都被要求在 <head> 元素之前注入一个 stub。在 stub 状态下调 getTCData 会返回 cmpStatus === 'stub' 和一个毫无意义的 tcString。永远别拿一个 stub 状态的响应去做数据采集决策。
6.2 OpenRTB:四个字段的世界
在 CMP 和后端之间,TC String 搭车在 OpenRTB 的扩展字段上。标准位置:
OpenRTB 2.5(扩展字段):
{
"id": "bid-request-id",
"imp": [...],
"user": {
"ext": {
"consent": "CPubcgAPubcgABABBENBgWAAAAAAAAAAAAAAAAEXkAAA.YAAAAAAAAAAA"
}
},
"regs": {
"ext": {
"gdpr": 1
}
}
}
OpenRTB 2.6 把它们提升为一等字段,并加入了 GPP:
{
"user": {
"consent": "CPubcgAPubcgABABBENBgWAAAAAAAAAAAAAAAAEXkAAA.YAAAAAAAAAAA"
},
"regs": {
"gdpr": 1,
"gpp": "DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
"gpp_sid": [2]
}
}
生产里你两套都得处理——SSP 异步升级,你会在同一个小时里同时见到 user.consent 和 user.ext.consent。一段防御式的提取:
function extractTcString(bidRequest: any): string | undefined {
return (
bidRequest?.user?.consent ??
bidRequest?.user?.ext?.consent
);
}
function extractGdprApplies(bidRequest: any): boolean {
const v =
bidRequest?.regs?.gdpr ??
bidRequest?.regs?.ext?.gdpr;
return v === 1 || v === '1';
}
6.3 服务端决策流程
bidder 收到请求后的决策流水线,大致是 gdpr → tc_string →(可选 CMP)→ publisher restrictions → vendor → purpose →(可选 special feature):
bidder 收到请求时的流水线。红色节点是硬停,琥珀色节点是降级为上下文,绿色是唯一一条全量个性化的出口。可选的第 3 步 CMP-ID 检查为清晰起见省略了(见下面细节 #2)。注意顺序:publisher restrictions 在 vendor/purpose 之前——restriction 能翻转一个 vendor 声明的法律依据,所以先检查那个位,就是在检查错的位。
所需 purpose 集合取决于这家 vendor 实际在做什么。一个典型 DSP 的最低配置:
| 操作 | 所需 purpose |
|---|---|
| 接收 bid request、上下文出价 | 1, 2 |
| 用第一方数据 / look-alike / 重定向 | 1, 2, 3, 4 |
| 归因与效果测量 | 1, 7 |
| 跨设备关联 | Feature 2 + 上述 |
| 用精确地理位置 | Special Feature 1 + 上述 |
四个安静地搞坏实现的细节
**1. vendor 检查放在 purpose 之前。**逻辑上这个检查是一个 AND——顺序不改变正确性。但运行上 vendor 优先更便宜:一次位查找,你的 vendor ID 是个常量,不匹配就短路掉整条请求。purpose 检查则是对着 GVL 声明走一遍数组,单请求成本更高。几乎所有生产代码都是 vendor.check() && purposes.check()。
**2. CMP 检查确实是可选的。**你可以拿 cmpId 去比对 IAB CMP List,确认字符串来自一个活跃、注册过的 CMP。实践中没人在热路径上这么做——IAB MO 在中心侧统一执行吊销,而给每条 bid request 加一次 CMP-list 查找带来的延迟,你不会想付。只有当你看到来自特定 CMP ID 的异常流量、想做防御式筛查时才加它。
**3. publisher restrictions 必须跑在 vendor/purpose 之前,而不是之后。**这是多数服务端搞错的那条规则。publisher restrictions 是覆盖——它能翻转一个 vendor 在 GVL 里声明的法律依据。如果某 vendor 把 purpose 2 声明在 LI 下,而媒体把它限制为 consent(类型 1),那检查 LI 位就是错的检查。先算出”这个媒体 × 这个 vendor × 这个 purpose 的有效法律依据”,再检查对应的位。
**4. special feature 有它自己的检查,不是默认的那个。**精确地理位置和主动指纹住在 SpecialFeatureOptIns 里,和 purpose 位分开。只在你真的需要那份数据时才检查它们。别把两个 special-feature 检查都塞进默认的 purpose 检查循环——对 99% 用不到它们的出价路径,那是白白浪费的 CPU。
最常见的那个合规失败:服务端代码检查了 vendor consent,却没检查 purpose consent。vendor 被授权了;用户从没同意过”建立画像”;DSP 却照样跑重定向。这是一次 GDPR 违规,不是一个边角情况。
6.4 兜底策略:几乎永远 fail-closed
当 TC String 缺失、解码失败,或解码出一个未授权状态时——你该怎么办?在 RTA 里这是个商业策略问题,有两个站得住脚的答案。在 TCF 里几乎从来不是。
| 场景 | 推荐动作 |
|---|---|
gdprApplies == 0 或缺失 | 当作非 GDPR 流量(信媒体的信号) |
gdprApplies == 1、TC String 缺失 | fail-closed:跳过出价,或跑一条仅上下文的兜底(purpose 1、2) |
| TC String 在但解码失败 | fail-closed:这是事故级——告警并记日志 |
| 解码成功但 vendor consent = 0 | 不出价 |
| 解码成功但 purpose consent 不全 | 降级为仅上下文 |
和 RTA 里 fail-open 是个正当商业选择不同,TCF 基本没有任何站得住脚的 fail-open 场景。这里的 fail-open,除了极少数例外,都是一桩正在酝酿的合规违规——而最坏情况下的代价,是那种能终结职业生涯的罚单。
为什么 fail-closed 是法律 + 协议 + 系统设计,而不是『最佳实践』
AdTech 圈子里 fail-closed 常被当成一个保守的惯例传来传去。它不是。它被三个相互独立的方向各自要求,任何一个都足够:
**1. 法律层:问责原则(Accountability Principle)。**GDPR Art. 5(2):“The controller shall be responsible for, and be able to demonstrate compliance with”——每个处理数据的实体都必须能够证明自己合规。“我的上游没拒我”不是一个可被采纳的抗辩。Art. 26(共同控制者——连带责任)和 Art. 28(处理者义务)再堵死另一条明显的逃路:合规义务不可转嫁。你没法把它外包给那个把请求发给你的 SSP。
2. 协议层:IAB 把 fail-closed 编成了一条强制规则。TCF Policy §12(4):
If a Vendor is unable to read or process the contents of a received Signal, the Vendor must assume that it does not have permission to store and/or access information on a device, or to process personal data for any Purpose and/or Special Purpose.
——一个缺失的信号,按规定就是”否”。不是一条软建议。§12(3) 进一步禁止在实时决策里依赖缓存的”旧的同意”;§12(5) 让”无法合规”在技术上等价于”不要行动”。把整个 §12 读一遍——它就是 GDPR 问责原则翻译成 vendor 的具体技术义务。
3. 系统层:SSP 是一个不可信的中间方。用分布式系统的话讲,SSP 和你的 DSP 分处一道信任边界(trust boundary)的两侧。SSP 把请求转发给你,并不意味着同意在上游被核验过。这就是 defense in depth、don’t trust your inputs、fail-safe defaults、zero trust——在网络侧它对应端到端原则(end-to-end principle):端到端的语义检查必须由端点来做,中间方的工作不算数。把上游当成一个过滤器,是经典的反模式。
合起来,一句话:“上游转发了 ≠ 你被授权去处理。” SSP 把决策交给你不是 bug,而正是 GDPR 加 TCF 加可靠系统设计共同要求的。而你唯一被允许把它交回去的方向,是 skip_bid()。
同样的原则,出现在任何真正的责任落在端点上的地方:
- 金融风控:对手方给你打了钱 ≠ 这钱是干净的——你仍要跑 KYC。
- 供应链合规:供应商发了货 ≠ 货是合规的——你仍要报关。
- API 安全:上游网关说请求有效 ≠ 你的服务可以信它——你要重新校验。
7. GPP:把 TCF 包进更大的信封
到 §6 结束,你已经能正确处理 GDPR 流量。但 TCF 只是一个法域。一家全球媒体至少要面对:
- EU/UK GDPR(TCF 为之设计的那个法域)
- California CPRA(CCPA 的升级继任者)
- 一串不断变长的美国州法——弗吉尼亚、科罗拉多、康涅狄格、犹他、得州、佛州、蒙大拿、俄勒冈、特拉华、爱荷华、内布拉斯加、新罕布什尔、新泽西、田纳西、明尼苏达、马里兰、印第安纳、肯塔基、罗德岛,每个立法季还在增加
- 加拿大 PIPEDA / 魁北克 Law 25
- 巴西 LGPD、中国 PIPL,等等
一家为每个法域都做一套独立传输的全球 DSP,会失去活下去的意志。IAB 在 2022 年推出 GPP(Global Privacy Platform),就是专门来阻止这件事的。
7.1 GPP 的设计:分节的信封
GPP 不是一个新的同意协议,而是一个信封格式——它把多个法域的同意字符串拼接起来,再前缀一个描述里面装了什么的头部。
GPP 是个信封,不是新的同意协议:各 section 用
~ 连接,gpp_sid 是告诉你哪些 section 在场的索引。注意首字符的提示——GPP 字符串是 D,原始 TCF 字符串是 C。
这个单字符的区别在实践里很有用——在任何生产日志里,瞄一眼首字符就知道你在看哪个协议。IAB 是故意把它做成兼容性标记的:base64 的 2 编码成 C,3 编码成 D。
每个 section 有一个对应法域的数字 ID。下面列出前十个——ID 11–27 沿着不断增长的美国州法名单继续往下(犹他、康涅狄格、佛州、蒙大拿、俄勒冈、得州等),完整表在 GPP Section Information 文档 里:
| ID | API 前缀 | 描述 |
|---|---|---|
| 1 | tcfeuv1 | EU TCF v1(已废弃) |
| 2 | tcfeuv2 | EU TCF v2——§3 讲的就是它 |
| 3 | — | GPP Header(必需) |
| 4 | — | GPP signal integrity |
| 5 | tcfcav1 | 加拿大 TCF |
| 6 | uspv1 | US Privacy(旧 CCPA 格式) |
| 7 | usnat | MSPA US National |
| 8 | usca | California(CPRA) |
| 9 | usva | Virginia |
| 10 | usco | Colorado |
| … | … | 美国州法 section 一直延续到 ID 27(罗德岛) |
除了独立的 section,GPP 还支持用 . 追加的可复用 subsection。实践中最重要的一个是 Global Privacy Control(GPC)——一个浏览器自身可经 Sec-GPC HTTP 头或 navigator.globalPrivacyControl 发出的 W3C 草案信号。CMP 会自动把它桥接进 GPP 字符串。**这意味着一个在浏览器里拨动了”do not sell”开关的用户,正向每一个 CMP 传递这个选择——他不必在每家媒体上分别点”拒绝”。**美国州监管者(尤其是加州)已开始把 GPC 当成一个具有法律约束力的 opt-out 信号,这让它在运营上变得重要。
7.2 gpp_sid:告诉你信封里装了什么的索引
gpp_sid 是一个数组,枚举这条 gpp 字符串里在场的 section ID:
{
"regs": {
"gpp": "DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNY",
"gpp_sid": [2, 6]
}
}
这里信封同时携带 TCF EU v2(section 2)和 US Privacy v1(section 6)。跨法域用户——VPN 用户、旅行者、双居所媒体——会经常产出这种”叠加”的信封。
处理顺序:
1. 若 regs.gpp 存在且 gpp_sid 非空:
解析 gpp_sid 列出的每个 section。
找到与本次请求相关的 section(EU 用户找 section 2)。
套用对应规则。
2. 否则回退到旧字段:
regs.gdpr + user.consent(仅 TCF),或
us_privacy 头(仅 CCPA)。
7.3 GPP 与 TCF 并存
那个标准困惑:**GPP 是不是取代了 TCF?**没有。
**TCF 仍是 EU/UK 的法域规则。**GPP 是一个传输与编码层,它的 EU section 里装着一条 TCF v2 字符串。生产里你会见到三种组合:
| 阶段 | bid request 里出现什么 |
|---|---|
| Legacy(GPP 之前) | 只有 regs.ext.gdpr + user.ext.consent(或 OpenRTB 2.6 的一等字段版本) |
| 过渡期 | TCF 字段和 regs.gpp + gpp_sid 都在,内容相同 |
| GPP-only(较新的 SSP) | 只有 regs.gpp + gpp_sid;TCF 字段省略——你必须从 GPP 信封里抽出 TC String |
生产代码三种都得处理:
function extractTcfTcString(bidRequest: any): string | undefined {
const gpp = bidRequest?.regs?.gpp;
const gppSid: number[] | undefined = bidRequest?.regs?.gpp_sid;
if (gpp && gppSid?.includes(2)) {
return extractSectionFromGpp(gpp, 2);
}
return bidRequest?.user?.consent ?? bidRequest?.user?.ext?.consent;
}
7.4 客户端:window.__gpp
类比 __tcfapi,IAB 定义了 window.__gpp:
window.__gpp('getGPPData', function(gppData, success) {
// gppData.gppString — 完整 GPP 字符串
// gppData.applicableSections — 如 [2, 8]
// gppData.parsedSections — 各 section 解析后的对象
});
window.__gpp('addEventListener', function(data, success) {
// 订阅同意变化
});
主流 CMP——OneTrust、Sourcepoint、Didomi、Usercentrics——都同时暴露 __tcfapi 和 __gpp,通常背后是同一套状态模型、两个 API 面。
一句话记住 GPP:它只换了信封,没换信。EU section 里仍是那条 TCF v2 字符串——你在 §3–§6 写的解码与决策逻辑原样复用,只是多了一步”从信封里取出 section 2”。
8. 生产环境的坑
事故案例里反复出现的那些模式。
8.1 GVL 版本不一致
GVL 大约每月发一个新版本。忘记同步的 vendor 会产出一个慢性 bug:
- GVL v320 把 vendor X 的某 purpose 从 LI 重新归类为 consent
- 你的服务端还引用着 v315 的快照
- 用户的 CMP 生成了一条基于 v320 的 TC String
- 你的授权逻辑和 CMP 对世界的认知对不上了
修法:每天同步最新 GVL、缓存它,并且在解码时,去查 TC String 自己声明的那个 GVL 版本(VendorListVersion)——而不是你当前的版本。历史版本全都保留在 https://vendor-list.consensu.org/v3/archives/vendor-list-v<N>.json。
8.2 缓存 TTL 活过了有效期
TC String 反映的是用户在某一刻的状态。用户可能立刻就:
- 打开 CMP 二级菜单、修改选择
- 半年后回来,遇到一个重新提示的 CMP
- 换设备(同意是逐设备的,不是逐用户)
如果你的 bidder 缓存了 TC String 或任何由它派生的标志,缓存 TTL 必须短于 TC String 的有效期(IAB 建议最长 13 个月,但多数 CMP 默认用 6 个月)。
经验法则:把同意派生的缓存 TTL 压到 1 小时及以下。每条 bid request 都应当从它实际带来的那条 TC String 重新派生决策,而不是从跨请求的粘性状态里取。
8.3 gdprApplies 的三态陷阱
在 CMP API 里,gdprApplies 是 true | false | undefined:
true——用户在 GDPR 范围内false——用户在范围外undefined——CMP 不知道(地理查找失败、IP 解析超时等)
IAB 对 undefined 的指引:当作 true。这里保守是对的。当它经 OpenRTB 传递时,对应状态是”regs.gdpr 缺失”,你的 bidder 应当当作 GDPR 适用。
最常见的那个 bug:把 undefined → false 映射,给后来证明是欧盟用户的人处理了数据,几个月后法务来要证据时才发现问题。
8.4 滥用 Legitimate Interest
在 TCF v2.0 早期,vendor 发现他们几乎可以把每个 purpose 都声明在 LI 下——“LI 默认开,用户得主动反对,通过率漂亮极了”。这扇门在 v2.2 被大幅关上:
- purpose 3、4、5、6 不再允许 LI
- vendor 必须为每个 LI purpose 提供具体的书面理由,由 IAB 审核
今天的安全假设:任何 LI 声明都得经得起法律审视。重定向和画像建立只能走 consent——别想着把它们绕道 LI。
8.5 忽略 Publisher Restrictions
读了 PurposesConsent 和 VendorConsents、却跳过 PublisherRestrictions 的服务端实现,大概率会处理媒体明确叫你别处理的数据。那既是对媒体的违约,也很可能是一次 GDPR 违规。
修法:先读 publisher restrictions、应用覆盖,再检查对应的位。
8.6 GPP 与 TCF 互相矛盾
过渡期里最常见的诡异状态:一条 bid request 同时带 user.consent(TCF)和 regs.gpp(含一个 TCF section 的 GPP 信封),而内容不同。通常是 SSP 侧的 bug——SSP 把一条 TC String 复制进了两个位置,但同意变化时只更新了一个。
保守的解法:优先用 regs.gpp(SSP 正在积极维护的较新标准),但记录每一次不一致,好拿去找 SSP。
8.7 iOS ATT 与 TCF 相互独立
App 开发者经常把 iOS ATT 和 TCF 混为一谈。它们是相互独立的法域规则:
- ATT(App Tracking Transparency) 是 Apple 的机制,控制 IDFA 访问,和 GDPR 或 TCF 没关系。
- TCF 是 IAB 的合规框架,和 Apple 的 ATT 没关系。
但在欧盟,iOS App 必须两者都满足:用户必须 (1) 在 ATT 提示里允许追踪,并且 (2) 在 CMP 里授予 TCF 同意。任一为否,IDFA 就无法用于跨 App 用途。
| ATT | TCF consent | 能力 |
|---|---|---|
| 允许 | 允许 | 完整功能 |
| 允许 | 拒绝 | IDFA 可访问,但法律上不可用 |
| 拒绝 | 允许 | 没有 IDFA——仅上下文 + SKAN |
| 拒绝 | 拒绝 | 同上——仅上下文 + SKAN |
9. 测试与可观测性
9.1 工具
- IAB TCF Compliance Validator——贴进一条 TC String,看每个解码后的字段。开发期必备。
@iabtechlabtcf/cmpapi——IAB 的参考 CMP API 实现。本地用它起一个假 CMP,把你的广告 SDK 对着每种同意状态测一遍。- Charles / mitmproxy——拦截 OpenRTB 请求,核验
regs.gdpr/user.consent/regs.gpp是否如预期出现。 - vendor 的 staging 模式——OneTrust、Sourcepoint、Didomi 都支持能模拟欧盟 IP、强制触发 CMP UI 的 staging 环境。
9.2 值得告警的指标
| 指标 | 你在盯什么 | 经验阈值 |
|---|---|---|
gdprApplies == 1 占总流量比 | 骤降 = CMP 误触发 | 偏离基线 ±10% |
| TC String 解码失败率 | 应为 0;任何上升都是事故 | > 0.1% |
TC String 缺失(在 gdprApplies = 1 时) | CMP 没写,或 SSP 没读 | > 1% |
| 你这家 vendor 的 consent 通过率 | 下跌 = CMP UI 的”全部拒绝”更显眼了,或你的 GVL 声明把 vendor 排除了 | 跌 > 5% |
| 你这家 vendor 的 LI 通过率 | 同上 | 跌 > 5% |
| 逐 purpose 的 consent 通过率 | 找出哪个 purpose 是你的瓶颈 | 跌 > 5% |
| GPP 出现率 | 跟踪 SSP 侧 GPP 铺开进度 | 单调上升 |
| GPP 与 TCF 不一致率 | 见 §8.6 | > 1% |
9.3 离线对账
每月一次,拿同一组字符串,把你服务端解码的输出和 IAB validator 解码的输出对账一遍。100% 一致,没有例外。任何不一致都是一桩潜伏的合规事故。
10. FAQ
Q1:我们不是 TCF vendor——我们只是把 DSP 当客户端用。为什么要关心 TCF?
因为当你上传受众、配置转化像素,或在 DSP 上搭重定向名单时,你的法律暴露在控制者那一侧——也就是你,广告主。懂 TCF 能告诉你:
- 你的哪些转化记录是真在 TCF 合规同意下采集的(哪些不是)
- 为什么上传的受众有时在 DSP 套用其同意过滤后大幅缩水
- 当法务问”我们的重定向名单合规吗”时,你该怎么回答
Q2:用户点了”全部拒绝”之后,我们还能做什么?
你可以做:
- 上下文定向——基于媒体内容选广告
- 当前会话内的 frequency capping(不用持久标识)
- 诚实的 SKAN / 仅 postback 归因
你不能做:
- 重定向或 look-alike 建模
- 跨 App 或跨站的画像建立
- 任何向设备写入持久标识的事——除非落在 purpose 1 那几个狭窄的豁免里
Q3:“全部拒绝”在 CMP UI 里真的是强制的吗?
是的。TCF v2.2 强制要求”全部接受”和”全部拒绝”在 CMP UI 的初始层上以同等显著度出现——这是 v2.2 较有影响的改动之一。它直接把那种”拒绝”藏进二级菜单、“接受”是个巨大绿按钮的暗黑模式设计判为违法。
Q4:如果我们永远拿不到 TC String,是不是就被彻底锁在出价之外了?
不一定。如果媒体判定非个性化广告是合适的,DSP 可以跑一条上下文路径(purpose 1、2)——但这条上下文路径必须完全不碰任何用户级数据,包括 cookie、设备 ID 和 IP(在 GDPR 下,IP 是 PII)。
实践中成熟的 DSP 会维护两条相互独立的代码路径:
- 完整路径——同意在场,全量重定向 + 画像 + 测量
- 上下文路径——同意缺失或部分,只用媒体提供的页面级信号(URL、内容类别、版位元数据),且不写任何持久标识
这里真正难的工程问题,是让两条路径都带着各自的兜底在生产里跑通,而不让彼此的数据交叉污染。
11. 速查表
- 是什么:标准化的同意合同,不是传输格式。一次曝光流经几十家公司,每家独立、实时判断”这个用户是否同意了这家公司对其数据的使用”。
- 三块基石:GVL(vendor 注册表 + 法律依据声明)、TC String(同意状态的物理载体)、CMP(页面上的生产者)。
- TC String:base64url 编码、
.拼接的分段位域。Core 必需,Disclosed Vendors(v2.3 起)必需,Publisher TC 可选——最后这个最常被漏读。 - OpenRTB 四字段:
regs.gdpr(或regs.ext.gdpr)、user.consent(或user.ext.consent)、regs.gpp、regs.gpp_sid。其余都是它们的下游。 - 服务端三步:解码 → 核验 vendor ID 被授权 → 按 GVL 声明的依据核验 purpose。判断永远是 vendor AND purpose。
- 顺序铁律:publisher restrictions 先跑——它能翻转 vendor 声明的法律依据;先检查位,就是检查错的位。
- 兜底:几乎永远 fail-closed。缺失 / 解码失败 / 未授权 = 不出价或降级为仅上下文。这是法律 + 协议 + 系统设计三方共同的要求,不是”最佳实践”。
- GPP:不是 TCF 的继任者,而是个信封——
gpp_sid是索引,section 2 装着 TCF v2 字符串;首字符D是 GPP、C是裸 TCF。 - 最常踩的坑:只查 vendor consent、不查 purpose consent;
undefined → false误映射;忽略 publisher restrictions;GVL 版本不一致;缓存 TTL 活过有效期。
12. 收尾
TCF 是那种典型的”实现简单、合规难”的协议。解码逻辑是一晚上的活儿。围着它的其余每一个工程决策——兜底策略、缓存 TTL、GVL 同步、vendor / publisher restriction 的优先级、GPP 过渡兼容——都直接接进法律暴露或商业收入。
一句话蒸馏:
协议是工程师的合同。合同被违反时,账单由法务来付。
别想绕过 TCF。别 fail-open。别拿一个 fail-closed 默认值去换那点边际增量流量。这不像 RTB 栈里别处那些软优化问题——在这儿出错的代价不是几次丢出价,而是六个月后以一封 CNIL(或下一个同类监管者)来信的形式交付的、全球年营收的一个百分比。**Criteo 的 4000 万欧元不是一个 bug,而是一次文档(证据链)的失败。**而 TCF 里的文档失败,意味着某处某人,fail-open 了。
把 §3、§4、§6、§8 弄懂,你就能负责任地在团队里跑起这套。§7(GPP)和 §9(可观测性)会在你公司走向全球、或监管堆上再添一部法律的那一刻起变成非做不可——而按过去这几年看,那是每六个月就会发生一次的事。
延伸阅读
先看同系列里和本篇直接相邻的几篇:
- 程序化广告生态全景:先看全局,再定位”同意 / 隐私”这一层在投放链路里的位置。
- App 广告 SDK 深挖:端上同意管理、ATT、SKAdNetwork 的承载——本篇正是那篇里”协议基础另文展开”的一块。
- RTA Explained:同样是”收到信号后该怎么决策”,但 fail-open / fail-closed 的取舍正好和 TCF 相反。
- Header Bidding:Prebid.js vs Prebid Server:TC String 搭车的那条 OpenRTB 竞价链路。
再是规范与一手资料:
法律 / 政策
- IAB Europe. Transparency & Consent Framework Policies:法律 + 政策义务的权威来源。Appendix A 是 §4 里 11 个 Purpose、3 个 Special Purpose、3 个 Feature、2 个 Special Feature 的原文出处。
- CNIL. Délibération SAN-2023-009 du 15 juin 2023(Criteo):4000 万欧元罚单背后的裁决。对任何维护 TCF 相邻系统的人,都是一份简短而清醒的读物。
协议 / 技术规范
- IAB Tech Lab. Consent string and vendor list formats v2(Core String 字段直达):TC String 编码、段结构与版本历史表(v2.2 砍掉 purpose 3–6 的 LI;v2.3 让 Disclosed Vendors 强制)。
- IAB Tech Lab. Global Privacy Platform — Section Information:§7 里 GPP section 表的出处,外加 GPC subsection 和
CvsD首字符约定。
工具 / 实现
- IAB Tech Lab. iabgpp.com Encoder / Decoder:官方在线工具。贴进一条
regs.gpp或user.consent,把每个字段当 JSON 读。§3.2 那条 TC String 就是用它解码的。
相关 / 相邻
- IAB Tech Lab. OpenRTB 2.6 Specification:
user.consent、regs.gdpr、regs.gpp、regs.gpp_sid的权威位置。 - IAB Europe. Global Vendor List:在线 GVL JSON 端点。服务端同步从这里起步。
- Apple Developer. App Tracking Transparency:用来内化为什么 ATT 和 TCF 是两套独立的法域规则。
附录:术语表
- ATT(App Tracking Transparency):Apple 在 iOS 14.5+ 的隐私机制,控制 App 对 IDFA 的访问。独立于 TCF。
- CMP(Consent Management Platform):媒体部署的组件,弹出同意 UI、收集用户选择、生成 TC String,并暴露
__tcfapi/__gpp。 - Core String:TC String 里永远在场的主段,装 purpose 的 consent / LI、vendor consent 与元数据。
- CPRA(California Privacy Rights Act):CCPA 的升级版,2023 年起生效;对应 GPP section 8。
- Data Controller(数据控制者):GDPR 术语,决定处理目的与方式的实体。媒体通常是控制者。
- Data Processor(数据处理者):GDPR 术语,代表控制者处理数据的实体。vendor 通常是处理者。
- fail-closed / fail-open:信号缺失时的默认行为;closed = 拒绝,open = 允许。在 TCF 里几乎永远 fail-closed。
- GDPR(General Data Protection Regulation):2018 年起生效的欧盟法规;TCF 的法律源头。
- GPC(Global Privacy Control):浏览器发出的、表示”do not sell”的 W3C 草案信号。作为 subsection 桥接进 GPP。
- GPP(Global Privacy Platform):IAB Tech Lab 的全球合规信封,把 TCF、US Privacy、各州字符串一起携带。
- GVL(Global Vendor List):IAB Europe 的 vendor 注册表。分配 vendor ID,记录每家 vendor 的 purpose / feature / 法律依据声明。
- IAB(Interactive Advertising Bureau):行业组织。TCF 由 IAB Europe 发布,GPP 由 IAB Tech Lab 发布。
- Legitimate Interest(LI,正当利益):GDPR 合法性基础之一。用户默认 opt-in、可 opt-out。v2.2 砍掉了 purpose 3–6 的 LI。
- OpenRTB:IAB 的实时竞价协议。TCF 与 GPP 经
user.consent/regs.gdpr/regs.gpp传递。 - Publisher Restrictions:媒体对 vendor GVL purpose 声明的覆盖(禁用 / 强制 consent / 强制 LI)。
- Purpose:TCF 的 11 个数据处理目的之一,从”存 cookie”到”个性化广告”。
- Service-Specific Scope:TC String 有效性限定在当前媒体。如今唯一合法的 scope。
- Special Feature:需用户显式 opt-in 的高敏感能力:精确地理位置、主动设备指纹。
- Special Purpose:用户无法拒绝的 LI-only 目的:反作弊、广告 / 内容投递、保存隐私选择。
- Stack:TCF 定义的 purpose / special feature 分组,用于 CMP UI 展示。
- TC String:base64url 编码的 TCF 同意状态——那个物理传输格式。
- Vendor:在 IAB GVL 里注册、带唯一 vendor ID 的实体。