Skip to content
Charles Shao
Go back

供应链透明度(上):ads.txt 与卖方授权声明

Updated:
–views

程序化广告的供应链天生面临被任意插队与冒名的风险。一次广告曝光(Impression)从媒体(Publisher)流向需求方(DSP),中途往往会经过多个 SSP、ADX 与转售中介。然而,在 OpenRTB bid request 里描述库存归属的关键字段——如媒体域名 site.domain、App 包名 app.bundle 以及卖方身份——历史上完全依赖上游自报。正因如此,自报机制为作弊者提供了可乘之机:只需把 domain 篡改为 nytimes.com,垃圾流量便能以优质库存的价格售出,这被称为域名伪造(domain spoofing);而将未授权库存层层转卖以洗白来源,则构成了非法转售或库存洗钱(inventory laundering)。

为了解决这一痛点,IAB Tech Lab 的供应链标准为这条链路逐层补上了可核验的声明机制。具体而言,媒体负责声明谁被授权销售,中介负责声明每个账号背后的真实身份,同时每条请求在运行时记录实际经过的每一跳。这套标准主要分为两类:三份静态声明(ads.txt / app-ads.txt / sellers.json)与一个运行时对象(SupplyChain / schain)。本篇将聚焦于三份静态声明,探讨如何界定「谁被授权参与」;至于运行时的 schain 与需求方交叉核验闭环,则在下篇《OpenRTB SupplyChain(schain)》中详细拆解。

本文是 供应链透明度与优化 系列的第 1 篇(静态声明)。 全系列 3 篇:

  1. 供应链透明度(上):ads.txt 与卖方授权声明
  2. 供应链透明度(下):OpenRTB schain
  3. 供应链优化(SPO)

一句话定位:利用 ads.txt 与 sellers.json 建立需求方与供给方双向交叉印证的静态授权清单,从源头阻断域名伪造与非法转售。

供应链透明度四件套总览:一条从左到右的供应链——媒体 Publisher(网站 / App,绿色)→ SSP / Exchange A(中介,蓝色)→ ADX B(转售中介,紫色)→ DSP 买方(竞价 + 核验,琥珀色),中介之间的箭头标注 schain 逐跳追加 node;每个节点上方挂着它发布的标准:媒体上方是 ads.txt / app-ads.txt,两个中介上方是 sellers.json,买方上方是交叉核验;下方两段说明——三份「声明」(ads.txt/app-ads.txt 媒体声明谁被授权卖、sellers.json 中介声明每个 account_id 背后卖方是谁、SupplyChain 运行时逐跳记录真实中介)加一个「运行时对象」;买方交叉核验:schain 每一跳的 (asi,sid) 查该 asi 的 sellers.json 得卖方域名、再回查该域名 ads.txt 是否授权,三者对得上且 complete=1 才是可信路径 四件套分两类:三份静态声明(ads.txt/app-ads.txt 由媒体发、sellers.json 由中介发,本篇主角)+ 一个运行时对象(schain 随每条 bid request 逐跳生长,见下篇)。声明回答「谁被授权参与」,运行时回答「谁实际参与了」;买方把「实际」和「被授权」一对,才知道这条路径可不可信。

TL;DR

Table of contents

Open Table of contents

一、问题定义:域名伪造与非法转售

在深入探讨供应链标准之前,我们需要先明确这些机制旨在防范何种风险。在 OpenRTB bid request 中,描述「库存归属」的关键字段主要包括:

site.domain / site.page      # Web:媒体域名 / 页面 URL
app.bundle / app.storeurl    # App:包名 / 商店链接
publisher.id / publisher.domain

这里的核心痛点在于,上述字段在流转沿途均由上游节点自行填写,需求方(DSP)根本无从验证其真伪。这种信息不对称直接催生了两类典型的欺诈行为:

对比这两种欺诈手段,两者的共同根因都在于供应链上缺失了一份「谁被授权代表谁」的可核验记录。为此,IAB 标准通过逐层补全这条信任链来破局:首先通过静态声明锁定「谁被授权参与」(即本篇重点),接着通过运行时对象记录「实际流转路径」(下篇重点),最终由需求方进行交叉核验以完成闭环。

为了更直观地理解库存洗钱,我们可以将其视作传统的洗钱过程,只是标的物变成了广告库存:

  1. 作弊或未授权的流量源 X,实际持有的是垃圾广告位、机器人流量,或者根本未获纽约时报授权却试图销售 nytimes.com 的库存;
  2. X 将该库存转售给 SSP-A,并伪称「这是 nytimes.com 的正规库存」;
  3. SSP-A(无论是因为疏于核查还是有意为之)将其转售给 ADX-B,ADX-B 随后再转售给 SSP-C,形成层层倒手;
  4. 当请求最终抵达需求方时,bid request 上的 site.domain 依然显示为 nytimes.com,完美伪装成了一条优质且正规的交易路径。

结果便是,需求方以优质库存的溢价买到了未授权的劣质流量。需要强调的是,域名伪造是直接篡改字段以假冒身份,而库存洗钱则是借助一串中间商的倒手来藏匿未授权来源(无证倒卖叠加隐藏路径),因此后者的隐蔽性与危害性往往更大。

这个洗钱案例将贯穿本系列的前两篇:本篇介绍的三份静态声明负责锁定「谁被授权」(例如洗钱链中的第一手 X 显然不会出现在 nytimes.com/ads.txt 名单内);而下篇介绍的 schain 则让「经了几手」彻底透明化。需求方只需将两者结合,逐跳回查,未授权的中间节点便会立刻穿帮。

二、ads.txt:媒体的授权卖方清单

ads.txt(Authorized Digital Sellers) 是整套标准中最早落地、也最为直观的一环。其核心机制是:媒体在自身域名的根目录放置一个纯文本文件(例如 https://example.com/ads.txt),以此向全网公开声明「哪些广告系统、使用哪个账号,被正式授权销售我的库存」。需求方与验证方只需定期爬取该文件,即可快速判断一条 bid request 中声称的卖方是否真正获得了媒体的认可。

文件中的每一行代表一条授权记录,字段之间以逗号分隔,最多包含四个字段:

ads.txt 一行的四个字段解剖 + app-ads.txt 的域名解析:顶部是一整行原始记录 google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0;下方拆成四个字段卡片——① 广告系统域名 asi(google.com,即 SSP/ADX)② 卖方账号 ID(pub-0000…,等于 sellers.json 的 seller_id)③ 账户关系(DIRECT / RESELLER)④ 认证机构 ID(TAG-ID,可选);中部旁注说明 DIRECT 是直接账户、RESELLER 是授权第三方转售,买方把全网 ads.txt 抓下来构建「域名→授权(asi,sid)集合」索引;底部是 app-ads.txt 的解析流水线:bundle id(com.foo.bar)→ 应用商店 listing → 开发者官网域名 developer.com → developer.com/app-ads.txt,并说明 App 没有 web root 靠开发者域名反查、这层映射依赖爬商店是最脏的一环 一行 ads.txt 的四个字段:广告系统域名 · 卖方账号 ID · 账户关系 · 认证机构 ID。第①②字段正是后面 schain 里 node 的 asi 和 sid,也是 sellers.json 的对账键——三份声明在这里第一次咬合。

这四个字段的具体定义如下:

字段含义说明
广告系统域名被授权的 SSP/ADX 域名对应 schain 节点中的 asi、sellers.json 的发布域名
卖方账号 ID媒体在该系统里的账号对应 schain 节点中的 sid、sellers.json 的 seller_id
关系类型DIRECT 或 RESELLERDIRECT 表示直接账户;RESELLER 表示授权第三方代卖
认证机构 IDTAG-ID(可选)用于进一步确认广告系统的官方身份

前两个字段 asi 和 sid 构成了贯穿整套标准的「对账键」,在后续的 sellers.json 与 schain 中会被反复提及:

正因如此,脱离了 asi 的单独 sid 是毫无意义的。两者必须成对出现:(asi, sid) 组合代表了「在 asi 这个广告系统内、编号为 sid 的卖方账号」。这一组合在全网范围内唯一锁定了一条「谁通过谁销售」的授权关系,并且只有当它在多份声明中完全一致时,才算真正对上了账:

ads.txt 一行     : asi = 广告系统域名, sid = 卖方账号ID
sellers.json 一条: asi = 发布该文件的中介域名, sid = seller_id
schain 一个 node : asi = node.asi,        sid = node.sid   (见下篇)

关系类型字段中的 DIRECT 与 RESELLER 则是理解供应链拓扑形状的关键:

为了更清晰地说明,我们以一条真实的 DIRECT 记录为例,将其四个字段映射到实际业务场景中:

google.com, pub-3469257745550756, DIRECT, f08c47fec0942fa0

这里需要澄清一个行业内常见的误解:在这条链路中,Google 扮演的是「销售库存的交易所」角色,而非「填充广告的广告主」。媒体将库存授权给 google.com 销售,随后 Google 发起实时竞价(RTB),撮合众多需求方参与竞拍,最终由出价最高者的广告素材填充该展示位;Google 在扣除技术服务费后,将剩余款项直接结算给媒体。简而言之,sid 回答「这是谁的账号」,asi 回答「谁在代其销售」,而 DIRECT/RESELLER 则回答「中间隔了几层」——至于最终填充进来的广告,则完全来自于竞价胜出的需求方,与 asi 本身无关。

至于第④个字段的认证机构 ID(如上例中的 f08c47fec0942fa0),需要单独加以说明。它是广告系统(asi)在第三方认证机构登记的全球唯一身份编号。在目前的行业实践中,该机构几乎专指 TAG(Trustworthy Accountability Group),因此该字段通常就是一个 TAG-ID。关于它有三个要点需要掌握:

此外,ads.txt 还支持若干变量行,以应对更复杂的库存所有权场景:

CONTACT=ops@example.com          # 运营联系方式
SUBDOMAIN=news.example.com       # 指向子域名专属的 ads.txt
OWNERDOMAIN=example.com          # 声明该库存真正的所有者域名
MANAGERDOMAIN=ssp.com            # 声明负责代运营变现的合作方

总而言之,ads.txt 的伟大之处在于,它将「谁能销售我的库存」这一原本属于媒体私下的商业约定,转化为了一份全网任何人都能抓取并核对的公开清单。需求方的应用逻辑极其简单:一旦发现请求中声称的卖方 (asi, sid) 不在清单内,即可直接判定为伪造或未授权。作弊者固然可以轻易篡改 bid request 中的流转字段,却绝无可能去修改 nytimes.com 根目录下那份由纽约时报官方严格控制的 ads.txt 文件。信任锚点由此从脆弱的「上游自报」成功转移到了「媒体自身掌控的公开文件」上,这正是整套供应链透明度设计的核心支点。

三、app-ads.txt:面向 App / CTV 的同一机制

ads.txt 的顺利运转高度依赖「域名根目录」这一天然锚点。然而,移动 App 与 CTV/OTT 场景并没有 Web 根目录的概念——像 com.foo.bar 这样的 bundle id 显然无法直接拼接出 /ads.txt 的访问地址。为了解决这一定位难题,业界推出了 app-ads.txt。其文件格式与 ads.txt 完全一致,唯一的差别在于需求方如何顺藤摸瓜找到这份文件:

  1. 首先,从 bid request 中提取出 app.bundle(包名)或 app.storeurl;
  2. 接着,利用该信息前往对应的应用商店(如 App Store 或 Google Play)查询该 App 的详情页(listing);
  3. 然后,从详情页中提取出开发者官方声明的官网域名(developer website);
  4. 最后,前往该域名的根目录拉取 https://developer.com/app-ads.txt。

通过这一流程,App 场景下的授权信任锚点被巧妙地转移到了「开发者在商店中声明的官网」上。因为应用商店的详情页是验证开发者身份的权威来源,作弊者极难冒充。这里的详情页(listing)正是用户在商店中浏览 App 时看到的包含图标、截图、评分以及关键的「开发者网站(Developer Website)」字段的页面;开发者在上架应用时必须经过商店的严格审核并登记该官网,攻击者根本无从篡改。

不过,在工程实现上,「从详情页提取开发者域名」的具体做法因平台而异:

在成功获取官网链接后,还必须进行归一化处理以提取根域名,然后再拼接文件路径:具体包括去除 www 前缀、清理路径与查询参数,并严格按照 public suffix list 提取 eTLD+1(例如将 https://www.developer.com/x?utm=.. 转换为 developer.com),最终才拼出 https://developer.com/app-ads.txt。

这条解析链路在逻辑上看似直白,但在工程实践中却是整套标准中最棘手的一环。建立并维护 bundle id → 开发者域名 的映射表,要求需求方持续不断地爬取各大应用商店并解析页面。在此期间,商店的 DOM 结构随时可能变动,且普遍存在严格的反爬策略与复杂的地区差异;更棘手的是,一旦 App 更换了开发者主体,或者 bundle 本身遭遇仿冒,映射关系便会立刻失效。因此,多数需求方要么投入重兵自行维护这张映射表,要么直接向第三方数据提供商采购现成的数据服务。

需要强调的是,许多 App 并没有配置 app-ads.txt,这在业务上往往是完全正常的。app-ads.txt 仅仅对那些「将库存投放至开放程序化市场、并依赖第三方 SSP 进行转售」的媒体才有实际意义。例如,挂载了数十个 SSP 进行变现的新闻聚合类 App,通常会维护一份长达上千行的 app-ads.txt;而像 Discord 这样的订阅或内购型应用、像 TikTok 这样广告仅在自家生态内闭环买卖而不走 OpenRTB 的围墙花园,以及各类纯工具类应用,由于根本不参与开放供应链的交易,自然无需发布该文件。因此,在核验过程中,「查不到 app-ads.txt」通常只意味着该 App 未接入开放程序化市场,而不能直接等同于欺诈——系统应将其归类为「降权或不适用」,而非粗暴地「拒绝」。反之,如果一条 bid request 声称自己是某围墙花园 App 的优质库存,却又携带着一长串外部 SSP 的 schain 节点,那才是真正需要高度警惕的异常信号。

至于 CTV 场景,情况则更为复杂:设备类型、分发渠道以及 App 形态极度碎片化,各家对于 app.bundle 的定义口径也难以统一,导致 CTV 领域的 app-ads.txt 覆盖率与准确率均远低于 Web 端。正因如此,CTV 至今依然是程序化广告欺诈的重灾区之一。

四、sellers.json:中介的卖方声明(ads.txt 的镜像)

前文提到的 ads.txt 与 app-ads.txt 均是从媒体视角出发的单向声明——它们回答了「谁能卖我」。然而,当需求方收到 bid request 时,直接对话的往往是中介(SSP/ADX),请求中携带的信息仅仅是「某广告系统内的某个 seller_id」。为了完成逻辑闭环,需求方迫切需要一份反方向的声明来确认:这个 seller_id 背后究竟对应着哪家实体?这便催生了 sellers.json。

按照规范,每个中介都必须在自身域名的根目录下发布一份 https://ssp.com/sellers.json,逐条声明其系统内每个账号背后的真实身份:

{
  "sellers": [
    {
      "seller_id": "540123",
      "name": "The New York Times",
      "domain": "nytimes.com",
      "seller_type": "PUBLISHER"
    },
    {
      "seller_id": "889001",
      "name": "SomeReseller Inc",
      "domain": "somereseller.com",
      "seller_type": "INTERMEDIARY"
    }
  ]
}

在这份文件中,每一条 seller entry 都可以视作中介为「系统内某个账号」签发的一张数字身份卡片。其中的四个核心字段分别回答了不同的业务问题:

除此之外,sellers.json 还支持两个可选标记:is_confidential(允许中介出于商业保密目的隐藏 name 和 domain,但代价是该路径将无法被完全核验,通常会被需求方降权处理)以及 is_passthrough(标记该节点仅作为技术透传通道,并非真正意义上的卖方账户)。

在文件的顶层结构中,还会携带中介自身的身份信息:如 contact_email、version,以及 identifiers(通常包含该中介的 TAG-ID,可与 ads.txt 的第④字段相互印证;有时还会包含 DUNS 邓白氏企业编码)。

关于如何获取这份文件:sellers.json 的存放位置完全由 asi 决定——即 https://{asi}/sellers.json。由于 asi 本身就是域名,因此可以直接拼接,完全省去了像 app-ads.txt 那样繁琐的域名翻译过程。在工程实践中,需求方会从抓取到的所有 ads.txt 中提取出全部的 asi,然后逐个拉取完整的 sellers.json 以备后续使用。(关于需求方如何将这三份声明抓全、清洗,并编译成能够在竞价热路径上实现毫秒级查询的索引,将在下篇的工程章节中详细剖析。)

综上所述,ads.txt 与 sellers.json 是一对方向相反、且必须互相印证的声明文件:

ads.txt 与 sellers.json 的双向对账:左侧绿色面板是媒体 ads.txt(nytimes.com),列出三行——openx.com 540123 DIRECT、pubmatic.com 771 RESELLER、appnexus.com 3722 RESELLER;右侧蓝色面板是中介 sellers.json(openx.com),列出两条 entry——seller_id 540123(name NYT、域名 nytimes.com、类型 PUBLISHER)与 seller_id 889001(SomeReseller、INTERMEDIARY);一条绿色箭头把左侧 openx 540123 那行与右侧 seller_id 540123 的 entry 连起来标注「对账」;底部旁注强调两份声明方向相反必须互相印证,ads.txt 从媒体说 openx 的 540123 能直接卖我、sellers.json 从 openx 说 540123 就是 nytimes.com,对得上卖方身份才成立,任一对不上都是伪造或非法转售信号 同一个 540123,在媒体的 ads.txt 里被声明为「openx 可 DIRECT 卖我」,在 openx 的 sellers.json 里被声明为「就是 nytimes.com 这个 PUBLISHER」。两个方向对上,卖方身份才成立;若 sellers.json 里 540123 指向的是别的域名,或 ads.txt 里根本没这条,就是可疑路径。

简而言之,ads.txt 代表了媒体侧的「谁能卖我」,而 sellers.json 则代表了中介侧的「这个账号是谁」。单份声明很容易被伪造,但两份对向声明要实现完美印证则门槛极高。至于如何将「实际经过了哪些中介」这条运行时路径接入,从而完成无死角的核验闭环,则要依靠下篇的 schain 来实现。

五、常见误区 ↔ 正解

常见误区正解
ads.txt 能「阻止」欺诈它仅提供可核验的授权清单;真正的拦截依赖需求方执行核验与拒绝。若媒体部署而需求方不核验,则毫无意义
ads.txt 和 sellers.json 是一回事两者方向相反:ads.txt 是媒体侧声明「谁能卖我」,sellers.json 是中介侧声明「账号是谁」,双向对账方能成立
app-ads.txt 就是把 ads.txt 换个名格式虽同,但定位机制截然不同:高度依赖 bundle → 商店 → 开发者域名 的反查映射,这也是最大的工程难点
RESELLER 路径都是可疑的RESELLER 属于合法授权的转售,真正可疑的是「对不上任何授权」的路径;需求方偏好 DIRECT 仅因其链路更短
App 没有 app-ads.txt 就有问题未必。订阅制、围墙花园(如 TikTok)或工具类应用本就不走开放程序化;该文件仅对参与开放转售的媒体有意义
认证机构 ID(TAG-ID)是硬核验键它绑定 asi、属于可选字段且现实中抄错率极高;核验主键始终是 (asi, sid),绝不能因 TAG-ID 异常而硬拒
部署了标准就 100% 干净鉴于覆盖不全、保密机制、CTV 口径混乱及映射滞后,核验永远是尽力而为,通常采用降权策略来兜底灰色地带

排障与调优口诀:认准对账主键 (asi, sid);警惕域名伪造;严查非法转售;理解 DIRECT 与 RESELLER 差异;宽容对待 TAG-ID 错误;妥善处理 confidential 节点。

当这三份静态声明成功对上账之后,拼图还差最后一块——运行时的「实际走了哪条路」。在下篇中,我们将引入 schain 把每一跳彻底摊开,看需求方如何利用 (asi, sid) 逐跳回查,最终完成无懈可击的核验闭环。


延伸阅读

同系列(供应链透明度与优化):

跨系列相关:

一手资料与规范:


–views
Share this post on:

Previous Post
供应链透明度(下):OpenRTB schain 运行时逐跳核验
Next Post
Last Look 深挖:从二价偷看反超到一价统一竞价