Best Practices for Ad Request Integration

Production-ready guidelines for integrating with the Ad Decision Engine.

Use POST Method for Production

Recommendation: Always use POST method in production.

Why:

  • Handles larger requests (no URL length limits)
  • Better for multiple impressions
  • Cleaner implementation
  • Better security (params not in URL)

Example:

// Recommended
fetch('https://csr.onet.pl/7012768/bid', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(bidRequest)
});

// Not recommended for production
fetch(`https://csr.onet.pl/7012768/bid?data=${encoded}`);

Generate Unique Request IDs

Recommendation: Include timestamp and random component in request IDs.

Why: Enables request tracing, debugging, and deduplication.

Examples:

// Good: Timestamp + random
const requestId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
// Example: "1734523456789-k3x9m2p"

// Good: UUID
const requestId = crypto.randomUUID();
// Example: "550e8400-e29b-41d4-a716-446655440000"

// Avoid: Sequential numbers
const requestId = "1"; // Bad - no uniqueness guarantee

Batch Multiple Impressions

Recommendation: Request multiple ad slots in a single API call.

Why: Reduces latency, improves performance, fewer HTTP connections.

Example:

{
  "id": "batch-request",
  "imp": [
    {"id": "header", "banner": {"format": [{"w": 728, "h": 90}]}},
    {"id": "sidebar", "banner": {"format": [{"w": 300, "h": 250}]}},
    {"id": "footer", "banner": {"format": [{"w": 728, "h": 90}]}}
  ],
  "site": {
    "id": "HOMEPAGE",
    "domain": "example.com"
  }
}

Performance gain: 3 separate requests → 1 request = 67% fewer HTTP calls.

Implement Retry Logic

Recommendation: Retry on server errors (5xx) and timeouts.

Why: Temporary failures can succeed on retry.

Rules:

  • Retry: 500, 502, 503, 504 errors
  • Don't retry: 400, 401, 403, 404 errors
  • Retry: Network timeouts
  • Don't retry: 204 (No bids)

Example:

async function requestBidWithRetry(bidRequest, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch('https://csr.onet.pl/7012768/bid', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(bidRequest)
      });

      // Don't retry client errors
      if (response.status >= 400 && response.status < 500 && response.status !== 429) {
        throw new Error(`Client error: ${response.status}`);
      }

      // Success or no bids
      if (response.ok || response.status === 204) {
        return response;
      }

      // Retry on server errors
      if (attempt < maxRetries - 1) {
        const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
        await sleep(delay);
        continue;
      }

      throw new Error(`Max retries exceeded`);

    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      await sleep(Math.pow(2, attempt) * 1000);
    }
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Backoff schedule: 1s → 2s → 4s

Handle No Bid Responses

Recommendation: Always have fallback content for 204 responses.

Why: No bids can happen anytime (budget exhausted, no matching campaigns).

Example:

async function displayAd(bidRequest) {
  const response = await fetch('https://csr.onet.pl/7012768/bid', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(bidRequest)
  });

  if (response.status === 204) {
    // No bids available - show fallback
    showHouseAd();
    return;
  }

  const data = await response.json();
  renderBid(data);
}

function showHouseAd() {
  document.getElementById('ad-container').innerHTML = `
    <a href="/advertise">
      <img src="/images/house-ad.jpg" alt="Advertise with us" />
    </a>
  `;
}

Respect Privacy Regulations

Recommendation: Always include GDPR and DSA flags for EU users.

Why: Legal compliance, user trust.

Example:

{
  "user": {
    "ext": {
      "npa": false
    }
  },
  "regs": {
    "gdpr": 1,
    "ext": {
      "dsa": 1
    }
  }
}

When to set npa: true:

  • User opted out of personalization
  • No consent for personalized ads
  • Privacy mode enabled
  • Children's content (COPPA)

DSA compliance:

  • Required for EU users
  • Response includes ext.dsaurl
  • Display link near ad: "Why this ad?"

Fire Impression Tracking

Recommendation: Always fire impression tracking URL when ad is displayed.

Why: Required for billing, campaign reporting, analytics.

Critical: Without firing tracking, impressions won't be counted and you won't receive credit.

Provide Site Context

Recommendation: Always include page URL and keywords when available.

Why: Improves targeting, increases bid prices, better ad relevance.

Example:

{
  "site": {
    "id": "TECH_SECTION",
    "domain": "example.com",
    "page": "https://example.com/articles/latest-smartphone-review",
    "ext": {
      "area": "TECH",
      "kwrd": "smartphone+review+technology+gadgets"
    }
  }
}

Impact: Up to 30% higher CPMs with good contextual data.

Include User Identifiers

Recommendation: Send user IDs when available (with consent).

Why: Enables frequency capping, better targeting, personalization.

Example:

{
  "user": {
    "ext": {
      "ids": {
        "lu": "202408221036415499301131",
        "aid": "hashed-email-or-user-id"
      }
    }
  }
}

Best practices:

  • Always hash logged-in user IDs before sending
  • Store lu in first-party cookie
  • Don't send if user opted out
  • Use consistent format across requests

Set Security Flag

Recommendation: Always require HTTPS creatives for secure sites.

Why: Prevents mixed content warnings, better security.

Example:

{
  "imp": [{
    "id": "1",
    "secure": 1,
    "banner": {
      "format": [{"w": 300, "h": 250}]
    }
  }]
}

Rule: If your site is HTTPS, always set secure: 1.

Support Multiple Ad Sizes

Recommendation: Provide multiple size options for better fill rates.

Why: More sizes = more ad inventory matches = higher fill rates.

Example:

{
  "imp": [{
    "id": "flexible-banner",
    "banner": {
      "format": [
        {"w": 728, "h": 90},
        {"w": 970, "h": 90},
        {"w": 970, "h": 250}
      ]
    }
  }]
}

Impact: 15-25% higher fill rates with flexible sizing.

Cache Bids Appropriately

Recommendation: Cache bids for short periods only.

Why: Fresh bids = better prices, current campaigns.

Rules:

  • Cache: 30-60 seconds max
  • Don't cache: Longer than 5 minutes
  • Cache key: Include page URL, user ID, timestamp bucket

Enable Compression

Recommendation: Use gzip/deflate compression for requests and responses.

Why: Reduces bandwidth, faster transfers.

Example:

curl -X POST "https://csr.onet.pl/7012768/bid" \
  -H "Content-Type: application/json" \
  -H "Accept-Encoding: gzip, deflate" \
  --compressed
fetch('https://csr.onet.pl/7012768/bid', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Accept-Encoding': 'gzip, deflate'
  },
  body: JSON.stringify(bidRequest)
});

Impact: 60-80% smaller response size.

Validate Requests Before Sending

Recommendation: Validate request structure before sending.

Why: Catch errors early, reduce 400 errors, better performance.

Validation checklist:

function validateBidRequest(request) {
  // Required fields
  if (!request.id) throw new Error('Missing id');
  if (!request.imp || request.imp.length === 0) throw new Error('Missing impressions');
  if (!request.site) throw new Error('Missing site');

  // Impression validation
  request.imp.forEach((imp, i) => {
    if (!imp.id) throw new Error(`Impression ${i} missing id`);
    if (!imp.banner && !imp.video && !imp.native) {
      throw new Error(`Impression ${i} missing format`);
    }
  });

  // Site validation
  if (!request.site.id) throw new Error('Missing site.id');
  if (!request.site.domain) throw new Error('Missing site.domain');

  return true;
}

// Use before sending
try {
  validateBidRequest(bidRequest);
  const response = await fetch('...', {body: JSON.stringify(bidRequest)});
} catch (error) {
  console.error('Invalid request:', error);
}

Use Connection Pooling

Recommendation: Reuse HTTP connections (keep-alive).

Why: Reduces connection overhead, faster requests.

Node.js example:

const https = require('https');

const agent = new https.Agent({
  keepAlive: true,
  maxSockets: 50,
  maxFreeSockets: 10,
  timeout: 60000
});

fetch('https://csr.onet.pl/7012768/bid', {
  method: 'POST',
  agent: agent,
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(bidRequest)
});

Impact: 20-30% faster subsequent requests.

Test with Demo Network

Recommendation: Use demo network ID for testing.

Demo Network ID: 7012768

Why: Always has active campaigns, safe for testing.

Example:

const NETWORK_ID = process.env.NODE_ENV === 'production'
  ? '7890123'  // Production network
  : '7012768'; // Demo network

const url = `https://csr.onet.pl/${NETWORK_ID}/bid`;