OpenRTB protocol
Native ads in OpenRTB
Native inventory is offered through imp.native, whose required request field is a JSON-encoded string, not an object: the markup of the separate OpenRTB Native Ads specification, serialized and embedded inside the bid request JSON. That double encoding is the classic native gotcha, on the way in and again on the way out through adm.
imp.native in the bid request
The Native object sits alongside banner, video, and audio in the impression object and declares the media type on offer. Its fields in OpenRTB 2.6:
request(string, required): the request payload complying with the Native Ad Specification, JSON-encoded. Since Native 1.1 the payload itself is the Native Markup Request Object; the 1.0-era rootnativewrapper node was dropped.ver(string, recommended): the Native Ads API version the request complies with, for example"1.2". Declare it; parsers behave differently per version.apiandbattr(integer arrays): supported API frameworks and blocked creative attributes, both drawn from AdCOM 1.0 lists as of 2.6.
Escaped, on the wire, a minimal native impression looks like this:
{
"id": "1",
"native": {
"ver": "1.2",
"request": "{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":157}},{\"id\":3,\"data\":{\"type\":1,\"len\":25}}]}"
}
}Note every quote inside request is escaped. If your serializer emits request as a bare object, strict receivers will reject the impression or silently drop native demand.
The Native Ads spec is its own document
The OpenRTB Dynamic Native Ads API spec (currently 1.2, final July 2017) defines the markup carried inside that string. The request object declares context and plcmttype (feed, in-content, outside content, widget), an optional plcmtcnt, and a required assets array. Each asset has an id, an optional required flag, and exactly one of title, img, video, or data. The asset id is the contract: the response echoes it so the buyer's headline lands in the publisher's headline slot.
| Asset | Type IDs | Notes |
|---|---|---|
title | no type IDs | Headline text. Request declares len (required), recommended 25, 90, or 140. |
img | 1 = Icon, 3 = Main | Image Asset Types list. 1.2 merged the earlier overlapping type 2 (logo) into type 1; icon is 1:1, main image has three supported aspect ratios. |
data | 1 = sponsored, 2 = desc, 3 = rating, 4 = likes, 5 = downloads, 6 = price, 7 = saleprice, 8 = phone, 9 = address, 10 = desc2, 11 = displayurl, 12 = ctatext | Data Asset Types list; 500+ reserved for exchange-specific types. |
video | no type IDs | Video as a native asset (not instream). Request carries mimes, minduration, maxduration, protocols, all required. |
eventtrackers vs imptrackers and jstracker
Native 1.2 replaced ad hoc tracking with a declared model. The request-side eventtrackers array states which events can be tracked (event: 1 = impression, 2 = viewable-mrc50, 3 = viewable-mrc100, 4 = viewable-video50) and by which methods (1 = img pixel, 2 = js). The response returns matching eventtrackers entries with URLs. The older response-side imptrackers (array of pixel URLs) and jstracker (an HTML blob) were marked to-be-deprecated in 1.2 and survive only for 1.0/1.1 compatibility. New integrations should declare and honor eventtrackers.
The response: another JSON string, in adm
Per the Native spec, the native creative "shall be returned as a JSON-encoded string in the adm field of the Bid object", so the response is double-encoded exactly like the request. The markup inside carries ver, the assets array with values filled in, a required link object for the click destination, and optionally eventtrackers and a privacy URL. Some exchanges instead accept a direct object in a custom field (a convention the spec itself acknowledges), but the portable form is the string in adm. Bid response validation is on the rtblint roadmap; today rtblint validates the request side.
Native in OpenRTB 3.0
OpenRTB 3.0 ends the double encoding. Creative structure moved into AdCOM 1.0, where a native ad is a structured ad.display.native object with asset objects defined in the same layer as the rest of the media description. There is no stringified sub-protocol; the trade-off is that 3.0 is a breaking change and most production traffic still runs 2.x. See OpenRTB versions for where 3.0 actually stands.
Frequently asked questions
Why is imp.native.request a string instead of a JSON object?
Because the Native Ads spec is a separate document versioned independently of OpenRTB, the core spec transports its markup opaquely: the native request is serialized to a string and embedded in the JSON bid request. OpenRTB 2.x officially supports only the JSON-encoded string form, though the Native spec notes many exchanges have implemented a direct object. Check integration docs before switching.
What changed between Native 1.1 and 1.2?
Native 1.2 (2017) deprecated the AdUnit and LayoutID fields that 1.1 had replaced with Placement and Context, added the eventtrackers request and response objects and marked imptrackers and jstracker as to-be-deprecated, added a privacy flag in the request and a privacy URL in the response, and added assetsurl third-party ad serving. 1.1 itself (2015) dropped the root native wrapper requirement from 1.0.
Where does the native response go in the bid?
The native creative is returned as a JSON-encoded string in the adm field of the Bid object, mirroring the request-side encoding. The Native spec notes that some implementers instead return a direct object in a separate field, which is an exchange-specific convention rather than the standard.
Validate it
Because request is a string, a malformed native payload hides inside a structurally valid bid request, and an unescaped object where a string belongs is a type error many pipelines never log. Paste the request into the bid request tester: rtblint checks imp.native against the 2.6 spec, reports type mismatches on request and ver with exact JSON paths, and flags unknown fields as openrtb.field.undefined. The same checks run in CI with the CLI.