DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

10 OpenRTB Native Mistakes That Pass Solo Validation and Kill Fill

In October 2025, DT Exchange documented native inventory on OpenRTB 2.5 with a changelog entry that adds native on Imp and a full Native object table. More SSPs are turning on Native 1.2 in-feed placements at the same time DSPs reuse banner QA on mtype: 4 bids. The painful pattern is unchanged: each JSON file validates in isolation, the exchange accepts the bid, and the slot renders empty or the buyer gets a loss reason that never showed up in preflight.

OpenRTB native is not one object. It is a string inside the bid request (imp.native.request) and another string on the bid (adm when mtype is 4). IAB Native Ads 1.2 puts required, asset types, and event tracker contracts in the first string. The second string must return matching asset ids and types. Solo validation never opens both documents.

Here are ten mistakes I still see on live traffic. Each one can return HTTP 200 from your own checks and still kill fill or spend.

  1. Treating imp.native.request as a nested JSON object on the wire. OpenRTB 2.x types request as a string. If your serializer emits a real object under imp.native, strict exchanges reject the impression. If your bidder parses the outer bid request and binds request to a struct without a second json.Unmarshal, you never read assets and bid blind. The encoding rule is spelled out in the native request string write-up and the OpenRTB native field guide.

  2. Double-encoding the request string. A common bug is calling JSON.stringify twice so request contains quoted JSON rather than a native markup object. Request-only validation may still pass if your linter never parses the inner layer. rtblint flags this as openrtb.native.request.double_encoded.

  3. Double-encoding adm on native wins. The same double-wrap shows up on the response side: adm parses to a string that contains another JSON document. Banner pipelines that stash HTML in adm accidentally reuse the habit on native. openrtb.bid.adm.double_encoded is the stable CI name.

  4. Declaring mtype: 4 with non-native markup in adm. Native responses must be a JSON object (Native Markup Response), not HTML and not a VAST URL unless the asset contract asked for video with an embedded VAST payload. A bid that sets mtype to 4 but ships banner HTML validates as a bid object and fails at render. Equativ documents the exchange-side outcome as loss code 1002 when the native response does not match the native request.

  5. Omitting an asset the request marked required: 1. Inside the decoded request string, any asset with required: 1 must appear in adm with the same id. A hero image plus click URL is a complete-looking creative that skips the mandatory headline. Response-only schema checks do not know id 1 was mandatory. Pair validation catches openrtb.native.asset.required_missing.

  6. Reusing an asset id with the wrong subtype. The request might ask for title on id 1 and the response returns img on id 1. ids line up in a diff tool, but the renderer expected text. This is the same pairing habit as required assets: keep the request fixture beside the response fixture.

  7. Shipping the legacy {"native": { ... }} wrapper in adm. Some older stacks wrapped the markup response one level deep. Native 1.2 expects the inner object at the top level of adm after you parse the string once. Validators that only know the modern shape miss the extra hop. See openrtb.native.request.legacy_wrapper for the request-side analogue.

  8. Ignoring eventtrackers the publisher declared. Native 1.2 lets the request specify impression measurement methods (image pixel, JavaScript, and so on). A response with no trackers when the request required them may still parse. You lose credit for the impression and the buyer disputes delivery. DT Exchange’s native table explicitly passes eventtrackers on every in-feed ask.

  9. Answering a ver: "1.2" request with 1.1-only response fields. Version skew across request and response strings produces subtle rejections: fields that existed in 1.1 but moved or tightened in 1.2. Validate both sides against the same ver before you scale a new SSP integration.

  10. Returning a native video asset without a VAST payload when the request asked for video. A native video asset is not a bare MP4 URL in every integration. Many templates expect a VAST document or URL inside the asset’s video object. The bid validates as JSON and the player has nothing to schedule. When the response embeds VAST, run the creative through the VAST tag tester and walk wrapper hops with the VAST inspector before you treat the native win as servable video.

How you catch it

Run three passes in CI, not one. First validate the bid request and parse imp.native.request as a string. Second validate the bid response and parse adm when mtype is 4. Third, pair them:

$ rtblint validate --type response --request req.json resp.json
Enter fullscreen mode Exit fullscreen mode

An implementation that lets a model emit OpenRTB, ARTF mutations, or AdCP-to-OpenRTB mappings still has to verify those strings on the wire. rtblint (cargo install rtblint, npm install rtblint-core, MCP rtblint-mcp) owns the bid object and the nested native payloads. It is independent of IAB Tech Lab and of AAO; the spec does not require it, but the contract does not move when an agent returns a compliant-looking bid.

Browse the full native rule set on the OpenRTB rules index when you wire new exchange profiles.

Where to go next

Top comments (0)