Integrate with the API (server)

Fetch the ad on your backend, render it in your server-rendered HTML, and fire the impression pixel from your server too.

Use this when the page is server-rendered and you want the ad in the initial HTML, or when you can't run client JS (Chrome extensions injecting from the background, native apps, strict CSP). The trade-off: you take on two responsibilities the browser handles automatically: forwarding the visitor's IP for dedup, and setting the Referer header on the pixel call.

1. Get your placement ID

Create a placement in the publisher dashboard and copy the ID. Register the domain you'll show ads on.

2. Fetch the ad (forward visitor metadata)

Call the serve endpoint from your backend. Forwarding the visitor's IP is required. Impression dedup hashes per IP: without it, every visitor is hashed under your server's single IP and you'll record one impression per hour total instead of one per visitor. Full parameter list: Serve endpoint parameters.

Almost every modern host puts a proxy or CDN in front of your app (Vercel, Cloudflare, Fly, AWS ALB, nginx). The visitor's real IP arrives in a forwarded header, not on the socket. Read it from X-Forwarded-For (first entry), CF-Connecting-IP, or whichever header your platform sets, otherwise you'll forward your edge node's IP and dedup collapses every visitor into one event per hour.

Node (built-in fetch, framework-agnostic):

// Read the visitor IP from the forwarded header your platform sets.
const xff = req.headers["x-forwarded-for"];
const visitorIp = (typeof xff === "string" ? xff.split(",")[0].trim() : "")
  || req.headers["cf-connecting-ip"]
  || req.socket?.remoteAddress
  || "";

const url = new URL("https://adventory.to/api/ad/serve");
url.searchParams.set("p", PLACEMENT_ID);
url.searchParams.set("ip", visitorIp);                  // required
url.searchParams.set("ua", req.headers["user-agent"] || ""); // recommended

const res = await fetch(url, { cache: "no-store" });
const { ad } = await res.json();

Python (requests, behind a proxy):

import requests

xff = request.headers.get("X-Forwarded-For", "")
visitor_ip = xff.split(",")[0].strip() if xff else request.remote_addr

params = {
    "p": PLACEMENT_ID,
    "ip": visitor_ip,                          # required
    "ua": request.headers.get("User-Agent"),   # recommended
}
r = requests.get("https://adventory.to/api/ad/serve", params=params, timeout=2)
ad = r.json().get("ad")

PHP (behind a proxy):

$xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
$visitorIp = $xff
  ? trim(explode(',', $xff)[0])
  : ($_SERVER['REMOTE_ADDR'] ?? '');

$query = http_build_query([
    'p'  => PLACEMENT_ID,
    'ip' => $visitorIp,                       // required
    'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '', // recommended
]);
$res = file_get_contents("https://adventory.to/api/ad/serve?$query");
$ad = json_decode($res, true)['ad'];

country is also accepted as an optional query parameter (ISO 3166-1 alpha-2). Skip it unless your platform exposes a geo-lookup header (e.g. CF-IPCountry on Cloudflare, X-Vercel-IP-Country on Vercel); the serve endpoint falls back to its own geo lookup if you don't pass it.

3. Render the ad

Mix the ad fields into your server-rendered HTML. Same field mapping as the browser path: product_name for the title, headline for the tagline, logo for the image, cta_text for the button label, colors.* for theming, click_url as the CTA href. Wrap the CTA with rel="noopener sponsored" (the sponsored rel marks the link as a paid placement, per the Google guidance for paid links). Full response shape: Serve response reference.

Don't forget the attribution: render one of ad.attribution.texts visibly near the ad, linked to ad.attribution.url. The attribution link is a disclosure (not a paid link), so use rel="noopener" only. Full rules: Attribution guidelines.

Before approval, your placement is in pending and serve returns a placeholder ad with impression_url: null. You can wire up rendering and click-through end-to-end during this window; nothing counts toward verification until the placement flips to active. Lifecycle: Verification.

4. Fire the impression pixel (set Referer)

Fire ad.impression_url from your server once the page has been served to the visitor. You must set the Referer header to the URL of the page where the ad was rendered. Server-side runtimes don't set it for you, and the impression endpoint validates it against the placement's registered domain. Pixel mechanics (dedup, caching): Impression pixel reference.

Use a Node runtime, not Edge. Vercel Edge, Cloudflare Workers, and Deno strip Referer from outgoing fetch as a forbidden request header, so your pixel call will arrive header-less and get rejected. Set export const runtime = "nodejs" in your route, or fire the pixel from a regular Node server. (The Node fetch sample below works in Node 18+.)

Node:

await fetch(ad.impression_url, {
  headers: { Referer: pageUrl },
});

Python:

import requests
requests.get(ad["impression_url"], headers={"Referer": page_url})

PHP:

$ch = curl_init($ad['impression_url']);
curl_setopt($ch, CURLOPT_REFERER, $page_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);

Why and what happens if you don't: Domain validation.

5. Confirm it's working

The first real impression flips your placement to active in the dashboard. See Verification, or Troubleshooting for the common server-side gotchas (missing Referer, missing IP, test origin in dev).

Reference