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.
Treating
imp.native.requestas a nested JSON object on the wire. OpenRTB 2.x typesrequestas a string. If your serializer emits a real object underimp.native, strict exchanges reject the impression. If your bidder parses the outer bid request and bindsrequestto a struct without a secondjson.Unmarshal, you never readassetsand bid blind. The encoding rule is spelled out in the native request string write-up and the OpenRTB native field guide.Double-encoding the request string. A common bug is calling
JSON.stringifytwice sorequestcontains 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 asopenrtb.native.request.double_encoded.Double-encoding
admon native wins. The same double-wrap shows up on the response side:admparses to a string that contains another JSON document. Banner pipelines that stash HTML inadmaccidentally reuse the habit on native.openrtb.bid.adm.double_encodedis the stable CI name.Declaring
mtype: 4with non-native markup inadm. 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 setsmtypeto 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.Omitting an asset the request marked
required: 1. Inside the decoded request string, any asset withrequired: 1must appear inadmwith 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 catchesopenrtb.native.asset.required_missing.Reusing an asset id with the wrong subtype. The request might ask for
titleon id 1 and the response returnsimgon 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.Shipping the legacy
{"native": { ... }}wrapper inadm. Some older stacks wrapped the markup response one level deep. Native 1.2 expects the inner object at the top level ofadmafter you parse the string once. Validators that only know the modern shape miss the extra hop. Seeopenrtb.native.request.legacy_wrapperfor the request-side analogue.Ignoring
eventtrackersthe 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 passeseventtrackerson every in-feed ask.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 sameverbefore you scale a new SSP integration.Returning a native video asset without a VAST payload when the request asked for video. A native
videoasset is not a bare MP4 URL in every integration. Many templates expect a VAST document or URL inside the asset’svideoobject. 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
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
- Start from a wire-correct native bid request example where
requeststays a string. - Read imp.native.request is a JSON string before you refactor serializers.
- Map exchange loss reasons to markup bugs with Equativ 1002 on native mismatch.
- Deep dive asset ids, required flags, and trackers in the OpenRTB native docs.
Top comments (0)