Blog · Supply chain

SupplyChain v1.1 adds nodes that never touch the money. Most schain checks assume those cannot exist.

The SupplyChain object has been frozen at version 1.0 since 2019, which is long enough that a lot of production code treats its rules as facts about the world rather than facts about one published document. SupplyChain v1.1 is in public comment until August 21, and the single change at its center invalidates the most common of those assumptions: that every node in the chain is in the payment flow. Under 1.1 a node can declare that it handled the request and was never paid for it.

What 1.0 actually says about hp

The hp field on a SupplyChainNode is defined as an integer indicating whether this node will be involved in the flow of payment for the inventory: 1 for yes, 0 for no. It is required. And then the specification adds the sentence that has shaped every implementation since: for version 1.0 of SupplyChain, this property should always be 1.

So the field is a boolean whose only legal value is true. The original design reserved the flag for exactly the case now arriving, which was foresight rather than an oversight, but for six years it has carried no information. Builders hardcode it, validators assert it, and buy-side audit logic walking the chain against sellers.json treats every node as a seller account it can look up, because in a 1.0 chain every node is.

The result is a chain that describes the money and nothing else. A bid request that passed through a header bidding wrapper, a publisher ad server, an SSAI stitcher and two SDKs arrives with a two-node chain, and both nodes are payment nodes. Four systems took custody of the payload and none of them are named anywhere in it.

What v1.1 proposes

IAB Tech Lab announced the proposal on June 23, 2026 and opened it for comment through August 21. The OpenRTB Working Group considered several shapes and recommended the one that changes the least structurally: custody-taking entities go into the same nodes array as everyone else, marked hp=0. There is no parallel array, no new object, no extension field. The chain gets longer and one flag starts meaning something.

The systems the announcement names as custody takers are the plumbing everybody already runs and nobody currently discloses: Prebid, ad servers, server-side ad insertion platforms, SDKs and wrappers. Read against a 1.0 chain, the new version is meant to let a buyer separate three things that are collapsed today: where the request originated, which systems handled it in flight, and which companies get paid.

The accompanying changes are the interesting part for anyone parsing this. Per the public comment issue in the Supply-Chain-Validation repository, the proposal revises the SupplyChain object description in OpenRTB, adds new enumerations for complete so the flag can express a 1.1 chain's notion of completeness, moves implementation guidance out of the OpenRTB page and into the dedicated repository, deprecates the original Supply Chain Object page, and updates sellers.json wording around the INTERMEDIARY seller type and the is_passthrough parameter.

One clause matters more than the rest for anyone who runs authorization checks: hp=0 nodes are not expected to pass ads.txt validation. Referencing a sellers.json entry for a non-payment handler is described as strongly recommended, not required. A custody node is a disclosure, not an authorization claim, and treating it as one will reject good supply.

The checks whose meaning changes

If you validate schain today, some of what you check is structural and survives, and some of it is a 1.0 assumption wearing a rule's clothing. The split:

CheckUnder 1.0Under a 1.1 chain
hp present and equal to 1Correct. The spec says it should always be 1.Wrong half the time. Presence is still required; the value is now real data.
ver equals "1.0"Correct. 1.0 was the only published version.A 1.1 chain fails a check that only accepts the old string, and the usual failure mode is a silent drop rather than a loud reject.
Hop count and path length policiesChain length approximates the number of intermediaries taking margin.Chain length now mixes payment hops and custody hops. Count hp=1 nodes if the policy is about money.
Every node resolves in sellers.jsonReasonable, since every node is a seller account.Too strict. A sellers.json reference on a custody node is recommended, not guaranteed.
ads.txt authorization per nodeApplied chain-wide.Applies to the payment path. Running it on hp=0 nodes manufactures violations.
Adjacent duplicate detectionCatches a forwarder that appended its node twice.Still valid, and more load-bearing: more appenders means more chances for two systems to write the same hop.
complete is 1Binary. The chain covers the whole path back to the publisher or it does not.New enumerations are proposed, so the flag stops being a plain boolean. Read the final text before branching on it.

RTBlint reports openrtb.schain.node.hp_missing when a node omits the flag, and the current guidance text says outright that in SupplyChain 1.0 chains it should be 1. That sentence has a shelf life now. The check itself does not change: hp is required on every node in both versions, and a builder that leaves it optional is the bug either way. What changes is that setting it correctly starts requiring the builder to know what its own system does.

Same story for openrtb.schain.ver_unexpected, which today flags anything other than "1.0" on the grounds that 1.0 is the only version ever published. That is true until it is not, and the transition period is where it gets expensive: a receiver that drops chains with an unrecognised ver will silently stop auditing the supply paths of every partner who adopts the new version first.

A chain before and after

A CTV request that moved through a publisher ad server, an SSAI platform and an exchange discloses two nodes today, because only two of those three get paid:

1.0
{ "ver": "1.0", "complete": 1, "nodes": [
  { "asi": "pubadserver.example", "sid": "8842", "hp": 1 },
  { "asi": "exchange.example",    "sid": "pub-42", "hp": 1 }
]}

1.1 (proposed shape)
{ "ver": "1.1", "complete": 1, "nodes": [
  { "asi": "pubadserver.example", "sid": "8842", "hp": 1 },
  { "asi": "ssai.example",        "sid": "net-19", "hp": 0 },
  { "asi": "exchange.example",    "sid": "pub-42", "hp": 1 }
]}

Three consequences fall straight out of that diff. A supply path optimization model that scores paths by length now sees a longer path for the same inventory, and will penalise the more honest disclosure unless it filters on hp first. Duplicate bid detection gets better, because the same request arriving through two routes now carries evidence of which systems it crossed. And an authorization check that walks all three nodes against ads.txt finds one that was never supposed to be there.

The Trade Desk's Rob Hazan framed the tradeoff in the announcement: a longer chain that accurately discloses the parties involved is better for buyers than a short chain that hides information. That is the right principle, and it only holds if the buy-side logic reading the chain stops treating length as a proxy for extraction.

The limits of reading a chain

Schain attracts more claims than it can support, so it is worth being exact about what a parser reading a bid request can decide. It can decide the structural questions: that ver is a recognised value, that complete carries a defined enumeration, that every node has a non-empty asi and sid, that hp is present and is 0 or 1, that no two adjacent nodes are identical, that the object sits at source.schain rather than the pre-2.6 source.ext.schain.

Whether the disclosure is true is a different question, and the payload has no way to answer it. Nothing proves that the SSAI platform in position two handled the request, or that a system handled it and appended nothing. That work happens in sellers.json crawls, ads.txt authorization and out-of-band audit, none of which v1.1 touches. What v1.1 gives you is the vocabulary for telling the truth, which is worth having and entirely separate from getting anyone to use it.

What to do before August 21

  • Grep for the hardcoded 1. Every schain builder in your stack that writes hp: 1 as a literal is now writing a claim it has not evaluated. Decide per system whether it is in the payment flow, and make the answer a configuration value rather than a constant.
  • Find every place that compares ver to a string. Accept 1.1 and read it, or reject it loudly with a logged reason. The failure you cannot afford is the third option, where an unrecognised version routes the chain into an unhandled branch and the request goes on without it.
  • Split hop counting from money counting. Any policy expressed as "reject chains longer than N" needs to state which N it means before longer chains start arriving.
  • Scope ads.txt checks to hp=1. Custody nodes are not authorization claims. Running the authorization path over them produces violations that are artifacts of your own reader.
  • Comment if you build this. The public comment issue in the Supply-Chain-Validation repository carries sub-issues for the open questions, which is a better place to raise a parser-level objection than a support ticket in six months.

The mechanical version of the first two is one call: paste a bid request into the tester, run the CLI over a corpus of what you actually emit, or give an agent the MCP server so it checks its own output. Field context for the surrounding machinery is in the supply chain reference and the supply chain trust stack.

Sources