The platform

Our own exchange, from request to rendered impression.

Adbustr Exchange is a first-party programmatic stack: supply intake, decisioning, auction, rendering, anti-fraud and analytics, written by our engineering team and serving from rtb.adbustr.com. Not white-label, not resold. Below is what runs underneath.

Sign-up is three screens: email, password, code. The console opens immediately; traffic starts after review.

Resonance

The decisioning layer runs inside the process that clears the bid.

An exchange is judged on one interval: the few hundred milliseconds between an ad request arriving and a cleared price leaving. Everything that decides the outcome (what the request is, whether it deserves demand, which partners see it, how the winner is priced) happens inside that interval, in one process.

Resonanceis that process. It is not a scoring service bolted onto someone else's stack: enrichment, quality filtering, demand selection, fan-out, clearing and settlement are stages of the same request path, and every stage records why it did what it did.

The exchange core is our own Go service, and the decisioning path runs inside the same process as the auction, with no network hop between the decision and the bid it governs.

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 / 2.6 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 / 2.6 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, so partners test one shape instead of per-publisher dialects.

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 to 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. House campaigns are matched first and external demand competes right behind them, inside one real-time auction decided in tens of milliseconds, 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 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 leaves in whatever shape the supply point speaks: VAST 2 / 3 / 4 InLine or Wrapper via VASTAdTagURI, banner HTML, ADM JSON, popunder JSON or an in-app SDK response (SDK path in pilot). Partner media arriving in resale supply is wrapped and passed through unchanged. 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, our 13.8 KB dependency-free player, an async banner tag, a JSON feed, a Prebid adapter, a server-to-server OpenRTB endpoint or the mobile SDK. Behind every one of them the same enrichment, fan-out and auction runs.

/vast/{type}GET · POSTVAST 2 / 3 / 4 XML · InLine or WrapperPublisher video players
/wrapper/{uid}GETVAST wrapperPlayer redirect chains
/rtb/{uid}GET · POSTOpenRTB 2.5 / 2.6 BidResponse · GET = pingServer-to-server supply partners
/bn/getGETBanner HTML container and loader (async, IntersectionObserver, srcdoc sandbox)Display placements
/bn/vpaidGETVAST wrapper around a partner-supplied VPAID media fileResale supply, pass-through only: we run no VPAID renderer
/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.

One boundary worth stating plainly: VPAID is not a format we render. Partner media that arrives in resale supply is wrapped and passed through unchanged, and the exchange carries no VPAID renderer of its own.

Auction and economics

One auction. Reconcilable per request.

House campaigns and external demand run through the same real-time auction, ours first and the outside market right behind. Bids arrive in the partner currency and are normalised against the rate table held in Redis at bid time, before they are ranked. The clearing price is bounded below by the applicable floor and above by the winner's own bid, and the price recorded for each request is available to the partner in the impression-level logs. We hand over the log, not a formula.

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 share is fixed at impression time, so a later report cannot rewrite what was already earned. 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, fixed at impression time
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, floors, bid rates, throttling, revenue share and 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

Nine checks at the front door, a reason on every rejection.

Nine checks before the auction

Every inbound request runs nine realtime checks, among them bot user agents, datacenter IP and ASN ranges, geo mismatch, duplicate requests, rate limits, Tor exits and managed lists. Each endpoint runs them in shadow or in block mode, so a partner sees what would be cut before anything is cut.

Contract checks at the door

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. Publisher-level badv blocklists travel with every outbound request.

Hourly source scoring

Supply sources are re-scored every hour on a 0 to 100 scale. A source that degrades goes into quarantine before it is banned, and the score is visible in the console rather than applied silently.

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.

Declarations, checked by a robot

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. A scheduled robot rechecks the ads.txt of connected properties and reports the result in the publisher console, so buyers never have to guess.

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.

Documented rejection codes

unknown_endpoint
Endpoint uid is unknown, or the endpoint is switched off
no_ip_ua
device.ip or device.ua missing from the request
browser_not_allowed
User agent does not map into the browser allow-list
non_mainstream
IAB25, IAB25-3 or IAB26 on a mainstream endpoint
fanout_off
Demand toggle off: request accepted and logged, demand untouched
no_imp
Empty imp array
no_feeds
No live demand feed attached to the endpoint
HTTP 400
Body does not parse as JSON
HTTP 204
No bid, for any of the reasons above

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. Nothing is dropped silently.

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

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 / 2.6, inbound and outbound
Impressions per request
Exactly one imp
Auction
Real-time auction · clearing bounded by floor and winning bid
Bid ranking
Norm = price × currency rate × BidRate
Bid discards
non-200 · empty · invalid JSON · price at or below zero · below floor
Auction timeout (tmax)
300 to 500 ms · configurable per publisher / zone
DSP timeout
500 ms · late responses discarded
Currency
Base currency · rates from the config snapshot at bid time
Notices
nurl (server-side) · burl (billable) · lurl (loss)
Price macros
AUCTION_PRICE · AUCTION_CURRENCY · AUCTION_ID · AUCTION_LOSS
Formats
banner · in-stream and out-stream video · CTV · push · popunder · native
JS player
13.8 KB · zero dependencies · six display formats
Display formats
slider · fullpage · inpage · embed · interscroller · floating
Header bidding
Prebid adapter · server-to-server feeds
Video mimes
mp4 · webm · javascript · 3gpp
Video protocols
[2, 3, 5, 6]
Video duration / bitrate
1 to 150 s · 10 to 9000 kbps
Anti-fraud
9 realtime checks per request · shadow or block per endpoint
Source quality scoring
Hourly · 0 to 100 · quarantine before ban
SCO compliance
source.ext.schain v1.0 on every bid request
sellers.json roles
PUBLISHER + INTERMEDIARY (BOTH)
ads.txt verification
Scheduled robot · status shown in the publisher console
Config reload
Redis snapshot · config:version / config:reload · no restart
Event pipeline
Own imp / click pixels → ClickHouse (log_rtb, log_event)
Reporting
Hourly marts · placement, creative, rejection reason · CSV export
Monitoring
Prometheus + Grafana · per format and per DSP
Console
React 19 · 2FA · 10 interface languages including RTL Arabic
Billing
Multi-currency double-entry ledger · scheduled payouts with net terms
Identity and privacy
First-party DMP segments · GDPR / TCF consent gate · opt-out cookie
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 are not asking the industry to trust us; we are asking it to verify us.

  • IAB Tech Lab
  • OpenRTB 2.5 / 2.6
  • VAST 2, 3, 4
  • Header bidding (Prebid adapter)
  • SupplyChain Object
  • ads.txt
  • app-ads.txt
  • sellers.json
  • GDPR
  • CCPA

Start here

Read the spec, or open the console and watch it run.

Publishers connect a site, an app or CTV inventory and get tags, player code or an OpenRTB endpoint. Advertisers launch banner, video, CTV, push, pop and native campaigns against moderated inventory. Both see fill, eCPM, spend and rejection reasons per placement in the same reports we read.

Sign-up is three screens: email, password, code. The console opens immediately; traffic starts after review.