(Click the image above to watch the YouTube video with Autoplay)
A comprehensive demonstration project showing how modern Ad Tech ecosystems work with seven components communicating via OpenRTB 2.5.
┌──────────────────┐
│ CTV Publisher │◄─── (video ad pods) ────┐
│ (:8096) │ │
└────────┬─────────┘ │
│ (bid/pod) │
▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Publisher │ │ SSP (Ad Server)│ │ DSP #1 (:8092) │
│ (:8090) │──────▶│ (:8091) │──────▶│ Demand Source │
│ │ bid │ │OpenRTB│ │
│ Tailwind Site │◀──────│ Auction Engine │◀──────│ Campaigns + │
│ + Prebid.js │winner │ + Inventory │ bids │ Creatives │
│ + ads.txt │ │ + DSP Registry │ └─────────────────┘
│ │ │ │ ┌─────────────────┐
│ │ │ │──────▶│ DSP #2 (:8093) │
│ │ ├──────────────────┤OpenRTB│ Demand Source │
│ │──────▶│ ADX (Exchange) │◀──────│ │
│ │ │ (:8094) │ bids └─────────────────┘
└────────┬─────────┘ └────────┬─────────┘ ────────┬────────
│ │ │
│ (logs) │ (logs) │ (logs)
▼ ▼ ▼
┌───────────────────────────────────────────────────────────────────────┐
│ AdView Real-Time Visualizer │
│ (:8095) │
└───────────────────────────────────────────────────────────────────────┘
# Build all components
make build
# Run all components (Publisher + CTV Publisher + SSP + 2 DSPs + ADX + AdView)
make run-all
# Or run individually
make run-publisher # http://localhost:8090
make run-ctv # http://localhost:8096
make run-ssp # http://localhost:8091/admin
make run-dsp1 # http://localhost:8092/admin
make run-dsp2 # http://localhost:8093/admin
make run-adx # http://localhost:8094/admin
make run-adview # http://localhost:8095
# Stop all components
make stop- Tailwind CSS styled content site.
- Prebid.js-like header bidding integration with both SSP and ADX.
- Banner ad slot (728×90) and Sidebar Rectangle slot (300×250).
- Serves
ads.txtandapp-ads.txtfor authorization compliance. - Reports client-side outbound request triggers and bid rewards to the visualizer.
- Netflix-Style UI: Sleek, immersive dark-themed wide-screen cinema experience.
- 10-Minute Slideshow Movie: Renders high-fidelity panning grids and drifting space star dust procedurally on an HTML5 canvas celebrating the "Antigravity Agentic Revolution".
- Linear Ad Pod Breaks: Programmatic 30-second ad break (featuring two back-to-back 15-second ads) scheduled at exactly
01:00or triggerable instantly using the cinema controller. - Parallel Pod Bidding: Automatically queries the SSP's
/bid/podendpoint with a multi-impression OpenRTB Video ad pod structure, applying brand deduplication to prevent same-brand duplicate playouts. - Procedural Ad Creatives: Parses won ad responses containing campaign-specific JSON block payloads and draws neon gold-themed promotional countdown cards directly on the canvas.
- Cinematic Sound Engine: Uses the Web Audio API to synthesize a deep ambient spacecraft cabin hum during the movie, combined with a high-pitched digital chime upon ad-break entry.
- Telemetry Hook: Integrates natively with the real-time logical clock staging engine and registers tracking events dynamically on AdView.
- Receives banner bid requests from the publisher and video ad pod requests from the CTV client.
- Sends OpenRTB 2.5 BidRequests to all registered DSPs in parallel.
- Runs auction (highest CPM wins) with custom floor price rules.
- Ad Pod Auction Routing: Formulates sequential video slot auctions (
RunPodAuction) evaluating competitive separation and returning multi-slot results back to the CTV player. - Real-Time Auction Log: Equipped with high-speed WebSockets (
/admin/ws) that stream active auction logs and collapsible raw OpenRTB JSON bids dynamically to the admin dashboard. - Dispatches asynchronous server-to-server transaction logs to the visualizer.
- Receives OpenRTB 2.5 BidRequests from SSP/ADX.
- Matches requests against active campaigns with available budget.
- Returns customized bids with beautiful creative markup or video-specific metadata JSON blocks.
- Frequency Capping & Brand Separation: Fully supports capping campaign impressions per user, and blocks duplicate campaign bidding inside a single multi-impression ad pod.
- Real-Time Bid Request Log: Automatically streams incoming OpenRTB Bid Requests JSON and transaction status over WebSockets directly to the dashboard.
- Reports outbound responses and "no-bid" payloads asynchronously to the visualizer.
- Operates as an independent Exchange server with distinct placement ID floor prices.
- Runs parallel RTB auctions among demand partners.
- Real-Time Transactions Log: Broadcasts all transaction details and JSON payloads to its live admin dashboard via WebSockets.
- Dispatches asynchronous server-to-server exchange transaction logs to the visualizer.
- Centralized real-time hub for all events occurring across the RTB ecosystem.
- Streaming Pipeline: Broadcasts unified events to connected browsers in real-time using high-speed WebSockets (
/ws). - Interactive Canvas Engine: Renders a gorgeous dark-themed HTML5 canvas designed for high-DPI displays.
- Dynamic Particles: Animates packet trajectories (requests, OpenRTB bids, responses, winners) as custom glowing circles with trailing paths and target collision ripple physics. Automatically maps
CTV-Publisherevents to the existing Publisher visual node. - Transaction Sidebar & modal JSON Inspector: Displays a live-updated event log, with interactive cards that slide open a comprehensive JSON inspector.
- Distributed Logical Clock Playout: Completely eliminates out-of-order visualization rendering by staging events for
220ms, sorting them by logical clock, and executing a staggered playout sequence.
Because network events originate from both client-side browsers (Publisher, CTV) and server-to-server endpoints (SSP, ADX, DSP) concurrently, network latency variations would normally cause visualization events to arrive out of order (e.g., DSP responses arriving before SSP requests).
To solve this and ensure visual timelines are flawlessly represented, we implemented a distributed logical clock sequencing pipeline:
- Stage 1 (Clock = 1): Publisher/CTV sends request streams out to SSP and ADX endpoints (logged by Prebid/CTV Client).
- Stage 2 (Clock = 2): SSP/ADX parse parameters, propagate the logical clock downstream, and multicast OpenRTB requests to registered DSPs (logged by Go SSP/ADX Auction Loop).
- Stage 3 (Clock = 3): DSPs read header propagation (
X-ClockandX-Auction-ID), increment the logical clock, and return their OpenRTB Bid Responses (logged by Go DSP Handler). - Stage 4 (Clock = 4): Publisher's client-side Prebid engine / CTV Player runs the local decisioning auction and logs the winning DSP and final reward status (logged by Prebid/CTV Client).
The visualizer's frontend employs an asynchronous staging queue:
- Bucket Aggregation: Groups incoming event logs in a temporary bucket for each unique
auction_idfor220ms. - Shift/Sort: Chronologically sorts events within the bucket according to their logical clock stage.
- Staggered Spawns: Triggers particle animations and appends sidebar entries with a
220msrelative delay per stage, guaranteeing a gorgeous, accurate sequence of the entire distributed auction journey.
Linear programming on Connected TV (CTV) introduces distinct user experience and structural bidding requirements compared to standard display web advertising:
An Ad Pod is a single advertising break consisting of multiple linear video creatives played sequentially (similar to commercial breaks on broadcast television). Instead of triggering individual, isolated auctions for each slot (which causes network overhead, user interface stuttering, and double-bidding), the CTV Publisher makes a single server-to-server request utilizing an OpenRTB multi-impression array.
- OpenRTB Multi-Imp Request: The CTV publisher issues a single request containing multiple
Impobjects under a unifiedImparray. EachImpdefines a sequential video sequence:"imp": [ { "id": "imp-pod-1", "video": { "w": 1920, "h": 1080, "sequence": 1, "minduration": 5, "maxduration": 30 }, "bidfloor": 1.50 }, { "id": "imp-pod-2", "video": { "w": 1920, "h": 1080, "sequence": 2, "minduration": 5, "maxduration": 30 }, "bidfloor": 1.50 } ]
A major issue in video streaming is competitive separation—preventing the same brand or campaign from buying back-to-back spots within the same commercial break (which frustrates viewers and wastes advertiser budget). To solve this:
- DSP Deduplication: In
dsp/handler.go, our bidding engine tracks campaigns inside a local transaction set (usedCampaigns). Once a campaign is selected to bid on the first slot (imp-pod-1), it is flagged and excluded from bidding on subsequent slots (such asimp-pod-2) within the same unified auction request. This prevents DSPs from self-competing and guarantees brand separation. - SSP Routing: In
ssp/auction.go, the SSP runs parallel multi-bid collections and assigns the highest distinct CPM bidder for each slot, managing price floor requirements.
Rather than relying on unreliable external video asset links, our CTV player leverages high-performance, responsive canvas painting to render custom sponsor frames:
- Draws moving geometric neon gold grids and glowing sponsored labels.
- Integrates a real-time countdown timer tracking individual commercial slots.
- Synchronizes with the Web Audio Synthesizer to emit premium notification chimes.
Copy .env.example to .env and modify as needed. Key settings:
| Variable | Default | Description |
|---|---|---|
PUBLISHER_PORT |
8090 |
Publisher web server port |
CTV_PORT |
8096 |
CTV Publisher web server port |
SSP_PORT |
8091 |
SSP server port |
DSP_PORT |
8092 |
DSP server port |
DSP_NAME |
dsp-alpha |
DSP instance name |
ADX_PORT |
8094 |
ADX Exchange server port |
ADVIEW_PORT |
8095 |
AdView visualizer server port |
DSP_ENDPOINTS |
http://localhost:8092,http://localhost:8093 |
List of active DSP endpoints |
AUCTION_TIMEOUT_MS |
200 |
Auction timeout in ms |
- Go (Latest) + Gin for high-performance servers
- Gorilla WebSockets for low-latency live log updates
- Tailwind CSS + Vanilla JS/Vue.js for modern, responsive dashboard designs
- OpenRTB 2.5 for standardized ad-tech network communication
- In-memory storage with thread-safe stores (data resets on restart, seeded with demo data)




