Impression Tracking
Praevis Exchange automatically injects impression tracking pixels into all ad responses. This guide explains how to integrate on your end to ensure accurate billing reconciliation.
Overview
Praevis automatically injects impression tracking into all winning creatives (Sprint 0.6.26 + Sprint 0.6.34). Your app aggregator needs to render the tracking pixels to confirm delivery.
Why Impression Tracking Matters
Problem: Discrepancy between auction wins (opportunities) and delivered impressions (confirmed renders).
Solution: Both sides count the same event — ads that actually rendered.
Auction wins: 10,000 impressions (opportunities)
Delivered: 7,500 impressions (confirmed renders)
Billing basis: 7,500 impressions ✓
Result: Billing matches actual deliveryHow It Works
Standard Flow (OpenRTB 2.6)
1. App sends bid request to Praevis
2. Praevis runs auction, returns winning creative
3. App receives creative in "adm" field
4. App renders the ad
5. Ad fires impression tracking pixel ← KEY STEP
6. Praevis receives impression confirmation
7. Both sides increment delivered impression counterImplementation
Banner/Display Ads
Praevis injects a 1x1 transparent tracking pixel at the start of the adm field.
Example response:
{
"seatbid": [{
"bid": [{
"adm": "<img src=\"https://rtb.praevis.com/track/impression?id=abc-123&pub=pub-001&partner=partner-001&dsp=dsp-001&price=2500000\" width=\"1\" height=\"1\" style=\"display:none;border:0;\" alt=\"\" /><div class=\"ad-creative\">Your ad here</div>",
"price": 2.50
}]
}]
}Your job: Render the complete adm HTML. The tracking pixel fires automatically when the ad loads.
Requirements:
- ✓ Render the complete
admHTML (including tracking pixel) - ✓ Don’t strip out
<img>tags or tracking URLs - ✓ Fire the pixel when the ad is actually visible (not just downloaded)
Video/VAST Ads
Praevis injects the tracking URL into the VAST <Impression> element (format-aware — doesn’t corrupt XML).
Example VAST response:
<VAST version="3.0">
<Ad>
<InLine>
<Impression><![CDATA[https://rtb.praevis.com/track/impression?id=abc-123&pub=pub-001&partner=partner-001&dsp=dsp-001&price=2500000]]></Impression>
<Creatives>
<Creative>
<Linear>
<MediaFiles>
<MediaFile>https://cdn.example.com/video.mp4</MediaFile>
</MediaFiles>
</Linear>
</Creative>
</Creatives>
</InLine>
</Ad>
</VAST>Your video player should automatically fire the <Impression> URL when the ad starts playing (standard VAST behavior).
Alternative: Server-to-Server Callback
If your app aggregator can’t render HTML pixels (e.g., native ads), use a server callback instead.
When ad renders in the app:
GET https://rtb.praevis.com/track/impression?id={auction_id}&pub={publisher_id}&partner={partner_id}&dsp={dsp_id}&price={price_micros}Parameters:
id- Auction ID (from bid response)pub- Your publisher IDpartner- Platform partner ID (if applicable)dsp- DSP ID that won (from bid response)price- Clearing price in micro-dollars (price × 1,000,000)
Example:
curl "https://rtb.praevis.com/track/impression?id=abc-123&pub=pub-001&partner=partner-001&dsp=dsp-001&price=2500000"Response:
- HTTP 200
- Content-Type: image/gif
- Body: 1x1 transparent GIF (43 bytes)
Testing
1. Send a Test Bid Request
curl -X POST https://rtb.praevis.com/rtb \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"id": "test-123",
"imp": [{
"id": "1",
"banner": {
"w": 300,
"h": 250
}
}],
"site": {
"id": "test-site",
"publisher": {
"id": "your-publisher-id"
}
}
}'2. Check the Response
Look for the tracking pixel in the adm field:
{
"id": "test-123",
"seatbid": [{
"bid": [{
"id": "bid-456",
"impid": "1",
"price": 2.50,
"adm": "<img src=\"https://rtb.praevis.com/track/impression?id=auction-789&pub=your-publisher-id&partner=partner-001&dsp=test-dsp&price=2500000\" width=\"1\" height=\"1\" style=\"display:none;\" /><div>Your ad here</div>"
}]
}]
}3. Render and Verify
Render the adm HTML in your app. The tracking pixel should fire automatically.
Verify it fired:
curl "https://rtb.praevis.com/track/impression?id=test-789&pub=your-publisher-id&partner=partner-001&dsp=test-dsp&price=2500000"Expected response:
- HTTP 200
- Content-Type: image/gif
- Body: GIF89a… (binary GIF data)
4. Check Metrics
After 24 hours of live traffic, compare metrics:
Praevis Dashboard:
- Wins: 10,000
- Delivered: 7,500
- Delivery Rate: 75%
Your Dashboard:
- Impressions Delivered: ~7,500 (should match Praevis delivered count)
Expected discrepancy: <5% (acceptable due to ad blockers, network failures, etc.)
Best Practices
When to Fire the Impression Pixel
✓ Fire when: Ad is actually visible in the app (viewable impression) ✗ Don’t fire when: Ad is just downloaded but not rendered ✗ Don’t fire when: User scrolls past ad before it loads
Industry standard: Fire when ad is 50% visible for 1 second (MRC viewability standard)
Fire Only Once
Each impression pixel should only fire once per ad render.
✗ Don’t: Fire on every scroll, resize, or focus event ✓ Do: Fire once when ad first becomes viewable
Handle Ad Blockers
Some users have ad blockers that may block tracking pixels. This is normal and expected.
Expected impression drop rate: 20-38% (industry standard)
- ~10-15% from ad blockers
- ~5-10% from network failures
- ~5-10% from viewability filters
Code Examples
JavaScript (Web)
// After receiving bid response
const bidResponse = await fetch('https://rtb.praevis.com/rtb', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'your-api-key'
},
body: JSON.stringify(bidRequest)
}).then(r => r.json());
// Extract ad markup
const adMarkup = bidResponse.seatbid[0].bid[0].adm;
// Render ad (tracking pixel fires automatically)
document.getElementById('ad-slot').innerHTML = adMarkup;
// Optional: Fire pixel only when ad is viewable
const adElement = document.getElementById('ad-slot');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
// Ad is 50%+ visible - tracking pixel fires automatically
observer.disconnect();
}
});
}, { threshold: 0.5 });
observer.observe(adElement);Swift (iOS)
// After receiving bid response
let adMarkup = bidResponse["seatbid"][0]["bid"][0]["adm"].stringValue
// Option 1: Render HTML in WKWebView (tracking pixel fires automatically)
webView.loadHTMLString(adMarkup, baseURL: URL(string: "https://rtb.praevis.com"))
// Option 2: Server-to-server callback (for native ads)
if isAdViewable() {
let trackingURL = extractTrackingURL(from: adMarkup)
URLSession.shared.dataTask(with: URL(string: trackingURL)!).resume()
}Kotlin (Android)
// After receiving bid response
val adMarkup = bidResponse.getJSONArray("seatbid")
.getJSONObject(0)
.getJSONArray("bid")
.getJSONObject(0)
.getString("adm")
// Option 1: Render HTML in WebView (tracking pixel fires automatically)
webView.loadDataWithBaseURL(
"https://rtb.praevis.com",
adMarkup,
"text/html",
"UTF-8",
null
)
// Option 2: Server-to-server callback (for native ads)
if (isAdViewable()) {
val trackingURL = extractTrackingURL(adMarkup)
OkHttpClient().newCall(Request.Builder().url(trackingURL).build()).enqueue(...)
}Troubleshooting
Tracking pixel not firing
Check:
- Is the
admHTML being rendered completely? (Don’t strip<img>tags) - Is your app blocking external image requests?
- Is Content Security Policy (CSP) blocking rtb.praevis.com?
- Are you using HTTPS? (HTTP pixels may be blocked)
Solution: Whitelist rtb.praevis.com in your app’s network security config.
Delivery rate too low (<50%)
Check:
- Are ads rendering below the fold? (Fire pixel only when viewable)
- Are network requests timing out? (Check app connectivity)
- Are you testing with ad blockers enabled?
Solution: Implement viewability detection (fire pixel when ad is 50% visible).
Delivery rate too high (>95%)
Check:
- Are you firing the pixel on ad download instead of render?
- Are you firing the pixel multiple times per ad?
Solution: Fire pixel only once, when ad is actually visible.
Privacy & GDPR
The tracking pixel is a first-party tracking call (to rtb.praevis.com, not a third-party tracker).
- No cookies are set
- No personal data is collected
- Only aggregate impression counts are stored
- GDPR compliant (no PII in tracking URL)
Billing Impact
Before Impression Tracking
Praevis Wins: 10,000 impressions
Praevis Charges: 10,000 × $2.50 CPM = $25.00
Publisher Delivered: 7,500 impressions
Discrepancy: -33% ($8.33 overbilled)After Impression Tracking
Praevis Wins: 10,000 impressions (tracked for reporting)
Praevis Delivered: 7,500 impressions (confirmed via pixel)
Praevis Charges: 7,500 × $2.50 CPM = $18.75
Publisher Delivered: 7,500 impressions
Discrepancy: <5% (acceptable variance)Result: Billing now matches actual delivery.
Support
Questions? Contact [email protected] or visit the dashboard support section.
Need help testing your integration? We can monitor impression tracking on our end and verify correct implementation. Contact support for assistance.