The platform

Our own exchange, from request to rendered impression.

Adbustr Exchange is a first-party programmatic stack — SSP, internal auction and bidder — written by our engineering team and serving from rtb.adbustr.com. Not white-label, not resold. Below is what runs underneath.

Request path

Eight steps between an ad call and a billable impression.

01

Intake

A format handler parses the incoming call — sid, zone, page, creative dimensions — or accepts a ready OpenRTB 2.5 BidRequest on /rtb/{uid}. A GET on the OpenRTB endpoint is treated as a connectivity ping, not a bid request. Each endpoint carries its own live toggle, held in the Redis snapshot and fail-safe in the off position: requests are still accepted and logged, demand is simply not spent.

02

Enrichment

The request is resolved against real client IP, GeoIP (ISO2 country, region, city, coordinates) and browscap-derived browser, OS and device class. Missing asset data degrades the enrichment, never the request — the auction still runs on whatever signal is available.

03

Canonical BidRequest

We build one canonical OpenRTB 2.5 BidRequest: exactly one imp, a site or app object, device, user, source.ext.schain, regs.ext with GDPR and consent state, badv from the publisher-level advertiser blocklist, tmax, cur and at. The same wire contract runs inbound from supply and outbound to demand — x-openrtb-version: 2.5 in both directions.

04

Demand selection

Resonance, our decisioning layer, narrows the DSP set before a single byte leaves the box: active and public flags, per-DSP geo lists, device-type rules, zone-level white and black lists, and the buyeruid requirement for partners that only serve synced users. Per-partner request adaptations are applied on top, so a DSP with non-standard field expectations gets a request it can actually parse.

05

Parallel fan-out

Every eligible DSP is queried concurrently inside tmax — 300–500 ms by default, configurable per publisher, zone and request, with a 500 ms per-DSP ceiling. Late responses are discarded rather than held. Each outcome is metered separately: request, bid, timeout, error, duration.

06

Auction

Bids are normalised to the base currency as Norm = price × currency rate × BidRate, then filtered: non-200 responses, empty bodies, unparseable JSON, price at or below zero, and anything under the applicable bidfloor are dropped. Clearing runs second price by default (at=2) and first price (at=1) per demand partner, with the clearing price bounded below by the applicable bidfloor and above by the winner's own bid. The exact price paid on any given request is reported in the impression-level logs; partners should reconcile against those logs rather than against a formula.

07

Settlement and notices

Price macros are substituted (AUCTION_PRICE, AUCTION_CURRENCY, AUCTION_ID and the rest), the winner nurl is called server-side, and every valid losing bid receives an lurl with a loss reason code. The burl travels inside the rendered creative and fires on the billable impression, not on the auction. For the OpenRTB channel the win notice can be proxied through /rtb/nurl/{id} so the demand-side notice lands on the partner billable impression.

08

Render and tracking

The winning creative is rendered into whatever the supply point speaks: VAST 3.0 InLine or Wrapper via VASTAdTagURI, VPAID, banner HTML, ADM JSON, popunder JSON or an in-app SDK response (SDK path in pilot). Our own impression and click pixels are injected at render time, so measurement does not depend on the buyer implementing anything.

Supply entry points

One auction core, every way in.

A publisher integrates the way its stack already works — a VAST tag, a JS tag, a JSON feed, a server-to-server OpenRTB endpoint or our SDK. Behind every one of them the same enrichment, fan-out and auction runs.

/vast/{type}GET · POSTVAST 3.0 XML — InLine or WrapperPublisher video players
/wrapper/{uid}GETVAST wrapperPlayer redirect chains
/rtb/{uid}GET · POSTOpenRTB 2.5 BidResponse · GET = pingServer-to-server supply partners
/bn/getGETBanner HTML container and loaderDisplay placements
/bn/vpaidGETVAST with VPAID media fileVPAID-capable players
/dsp/{pubid}GET · POSTADM — JSON with ready markupClient-side players
/pop/{sid}GET · POSTJSON — url, price, cur, imp and click pixelsPopunder script
/sdk/v1/adPOSTIn-app JSON — native assets, media URL, imageUnity and Android SDK — pilot, transacted as site inventory
/rtb/nurl/{id}GETWin-notice proxyDemand notices on the OpenRTB channel

Supply is modelled as Publisher → Zone → per-DSP configuration. A zone carries its own format, DSP white and black lists, and per-partner overrides of bidfloor, BidRate and ReqFilter — either merging with or replacing the demand-side defaults.

Auction and economics

Second price by default. Reconcilable per request.

Bids arrive in the partner currency and are normalised against the rate table held in Redis before they are ranked. The default clearing rule is second price, bounded below by the floor and above by the winner's own bid; first price is a per-partner setting, declared in the request as at. The clearing price recorded for each request is available to the partner in the impression-level logs.

Payout on a supply link is revenue multiplied by the revenue share for that country, falling back to the endpoint base rate when no country rule exists. The platform margin is what remains. Every one of these values is read from the database at serve time — none of them is compiled into the binary.

bidfloor
Minimum price for a bid to participate — set on the zone and on each demand link
BidRate
Ranking multiplier that normalises how a partner prices its bids
ReqFilter
Share of requests actually routed to a given DSP — throttling without disconnecting
Revenue share
Base percentage on the endpoint plus per-country (ISO2) overrides
tmax
Auction timeout, set per publisher, zone or request
maxqps
Per-endpoint request-rate ceiling

Configuration

Change the exchange without restarting it.

The entire serving configuration — publishers, zones, demand feeds, OpenRTB endpoints, currency rates — lives as a snapshot in Redis. Sync jobs project it from the source databases, bump config:version and publish config:reload; the serving process rereads the snapshot in place. No deploy, no restart, no dropped requests.

The same mechanism carries the live toggle on every OpenRTB endpoint. It is fail-safe in the off position: traffic keeps being accepted and logged while no demand is spent, which makes ramp-ups and incident containment a one-key flip rather than a release.

Sync jobs run inside the private network and talk only to our own database instance. The serving contour reaches no external hosts.

Traffic quality

Filtered at the front door, logged with a reason.

Inbound request filters

Every inbound OpenRTB request is checked before it costs anyone anything: device.ip and device.ua are mandatory, the user agent must resolve to our browser allow-list, and IAB25 / IAB25-3 / IAB26 categories are cut from site.cat and site.sectioncat when the mainstream filter is on. Each rejection is logged with its reason code.

Reason-coded rejection log

Every rejected request is written to the funnel log with a machine-readable reason code — no_ip_ua, browser_not_allowed, non_mainstream and the rest — so any filtering outcome can be reconstructed for a given time window. We do not run proprietary or third-party invalid-traffic scoring in the bid path; the Anti-Fraud Policy states plainly which controls exist and which do not.

Supply-chain declarations

We publish sellers.json, ads.txt and app-ads.txt at their canonical locations and pass source.ext.schain (v1.0) on every outbound bid request. Automated validation of counterparty supply files is not implemented today; counterparty chains are reviewed manually at onboarding.

OpenRTB response validation

Demand responses are validated against the contract, not just accepted: currency, secure adm for HTTPS inventory, bcat / bapp / battr, seat allow and block lists, video protocols and creative dimensions against the imp we sent.

Supply-chain transparency

A SupplyChain object (source.ext.schain, version 1.0) is attached to every demand request. Publisher-level badv blocklists and site.cat categories travel with it, imp.secure is propagated, and our sellers.json declares seller_type where the role applies.

Per-request verdict trail

Nothing is dropped silently at the endpoint level. Each inbound request lands in the funnel table with a bid or nobid verdict, a reason, geo, domain, format, the demand feed selected, the bid, the cleared price and the revenue share applied.

Documented rejection codes

  • unknown_endpoint
  • no_ip_ua
  • browser_not_allowed
  • non_mainstream
  • fanout_off
  • no_imp
  • no_feeds
  • HTTP 204
  • HTTP 400

A no-bid is an HTTP 204 with a reason attached to the request in our logs. Integration partners get the code, not a shrug.

Observability

Every request accounted for, twice.

Rendered creatives carry our own impression and click pixels. A dedicated collector batches those events into ClickHouse — log_rtb and log_event hold the impression and event streams, and a per-request funnel table records the verdict, reason, geo, domain, format, demand feed, bid, cleared price, margin and revenue share for every inbound OpenRTB request.

Materialized views aggregate by minute and by day. An incremental ETL job upserts into the reporting stores behind the partner cabinets using a stable hash key, so a rerun corrects data instead of doubling it. Partners read the same numbers we do, through self-serve publisher and advertiser cabinets and a reporting API.

The serving process exports Prometheus counters, gauges and histograms labelled by format and by demand partner — requests, bids, timeouts, errors and latency — into Grafana dashboards. When a DSP degrades, we see it in its own series before anyone reports it.

Specifications

Technical specifications

Core
Go · fasthttp
Wire contract
OpenRTB 2.5 — inbound and outbound
Auction timeout (tmax)
300–500 ms · configurable per publisher / zone
DSP timeout
500 ms · late responses discarded
Auction type
Second price (at=2) default · first price (at=1) optional
Bid ranking
Norm = price × currency rate × BidRate
Impressions per request
Exactly one imp
Video mimes
mp4 · webm · javascript · 3gpp
Video protocols / api
[2, 3, 5, 6] · [1, 2] (VPAID 1.0 / 2.0)
Video duration / bitrate
1–150 s · 10–9000 kbps
SCO compliance
source.ext.schain v1.0 on every bid request
sellers.json roles
PUBLISHER + INTERMEDIARY (BOTH)
Config reload
Redis snapshot · config:version / config:reload · no restart
Event pipeline
Own imp / click pixels → ClickHouse (log_rtb, log_event)
Monitoring
Prometheus + Grafana · per format and per DSP
In-app SDK
Pilot — Unity 2021.3 LTS+ · Android minSdk 21 · no app object emitted yet
Auth
IP allow-list + bearer token, agreed at onboarding
Serving locations
EU (Amsterdam) + US (Oregon) — dedicated app and db instances, private network

Standards

Connected through standards.

Adbustr is built on top of, not around, the standards the IAB Tech Lab has spent a decade defining. We're not asking the industry to trust us — we're asking it to verify us.

  • IAB Tech Lab
  • OpenRTB 2.5
  • VAST 3.0
  • VPAID 1.0 / 2.0
  • SupplyChain Object
  • ads.txt
  • app-ads.txt
  • sellers.json
  • GDPR
  • CCPA