Integrate the EventZR ad serving engine into your application. Learn placement formats, impression/click tracking, server-side HMAC verification, and rate limits.
When your application needs to display an ad, it sends a request to the ad serving endpoint with placement details and user context. The serving engine runs an auction among eligible campaigns, selects the winning creative, and returns it for display.
1. Client sends POST /ads/v1/serve with placement + context
2. Serving engine matches active campaigns by audience + placement format
3. Auction runs among eligible campaigns (bid strategy + budget check)
4. Winning creative returned with impression + click tracking URLs
5. Client renders the creative and fires the impression URL
6. On click, client redirects through the click tracking URL
import { AdsClient } from '@eventzr/ads-client';
const client = new AdsClient({
baseUrl: 'https://api.eventzr.com/ads/v1',
tenantId: '<tenant-id>',
accessToken: '<jwt>',
});
const response = await client.serve.requestAd({
placementId: 'sidebar-banner',
format: 'banner',
width: 300,
height: 250,
context: {
pageUrl: 'https://yoursite.com/events/summer-fest',
category: 'events',
keywords: ['music', 'festival', 'summer'],
userAgent: navigator.userAgent,
locale: 'en-US',
},
});
if (response.data) {
const ad = response.data;
// ad.creativeUrl - Image/video source URL
// ad.headline - Ad headline text
// ad.body - Ad body text (for native ads)
// ad.callToAction - CTA button text
// ad.clickUrl - Click-through URL (tracked redirect)
// ad.impressionUrl - Pixel URL to fire on view
// ad.adId - Ad ID for reference
}The Ads API supports four placement formats. Specify the format in your ad request to receive the appropriate creative type.
| Format | Common Sizes | Description | Response Fields |
|---|---|---|---|
| banner | 728x90, 300x250, 160x600, 320x50 | Standard display banners (image or HTML) | creativeUrl, clickUrl |
| native | flexible | Native ads that blend with your content feed | headline, body, imageUrl, callToAction |
| video | 16:9, 9:16, 1:1 | Pre-roll, mid-roll, or standalone video ads | videoUrl, duration, vastTag |
| sponsored | flexible | Sponsored content cards for event listings | headline, body, imageUrl, sponsorName |
When a page has multiple ad slots, use the batch endpoint to fetch all ads in a single network call, reducing latency.
const batchResponse = await client.serve.requestBatch({
placements: [
{
placementId: 'header-leaderboard',
format: 'banner',
width: 728,
height: 90,
},
{
placementId: 'sidebar-rectangle',
format: 'banner',
width: 300,
height: 250,
},
{
placementId: 'feed-native-1',
format: 'native',
},
],
context: {
pageUrl: 'https://yoursite.com/events',
category: 'events',
},
});
// batchResponse.data is an array matching the placements order
batchResponse.data.forEach((ad, index) => {
if (ad) {
renderAd(ad, index);
}
});Accurate tracking is critical for billing and campaign optimization. Fire the impression URL when the ad becomes viewable, and route clicks through the click URL.
// Fire impression when ad is at least 50% visible for 1 second
// Use IntersectionObserver for viewability tracking
function trackImpression(impressionUrl: string) {
const img = new Image();
img.src = impressionUrl; // 1x1 pixel request
}
// Or use the SDK for server-side tracking
await client.impressions.track({
adId: ad.adId,
placementId: 'sidebar-banner',
timestamp: new Date().toISOString(),
viewability: 0.75, // 75% visible
});// Clicks go through the tracked redirect URL
function handleAdClick(clickUrl: string) {
// The clickUrl includes tracking params and redirects to destination
window.open(clickUrl, '_blank', 'noopener,noreferrer');
}
// Or track server-side with the SDK
await client.clicks.track({
adId: ad.adId,
placementId: 'sidebar-banner',
timestamp: new Date().toISOString(),
});For server-side impression and conversion tracking, verify the authenticity of tracking callbacks using HMAC-SHA256 signatures. This prevents fraudulent impression or conversion injection.
import { createHmac } from 'crypto';
function verifyTrackingSignature(
payload: string,
signature: string,
secret: string,
): boolean {
const expectedSignature = createHmac('sha256', secret)
.update(payload)
.digest('hex');
return expectedSignature === signature;
}
// In your tracking webhook handler
app.post('/tracking/callback', (req, res) => {
const signature = req.headers['x-eventzr-signature'] as string;
const payload = JSON.stringify(req.body);
const secret = process.env.ADS_WEBHOOK_SECRET!;
if (!verifyTrackingSignature(payload, signature, secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process the tracking event
processTrackingEvent(req.body);
return res.status(200).json({ received: true });
});Ad serving endpoints have rate limits based on your subscription tier to ensure fair usage and platform stability.
| Tier | Ad Requests/min | Batch Size | Impressions/day |
|---|---|---|---|
| Startup | 100 | 5 | 10,000 |
| Pro | 500 | 10 | 100,000 |
| ProMax | 2,000 | 20 | 500,000 |
| Enterprise | 10,000 | 50 | Unlimited |
When rate-limited, the API returns 429 Too Many Requests with a Retry-After header.
Only fire impression tracking when the ad is actually visible in the viewport. This improves reporting accuracy and prevents wasted impressions.
The serve endpoint may return null if no matching ads are available. Always check for a null response and display fallback content or collapse the ad slot.
Append a timestamp or random value to impression URLs to prevent browser caching from suppressing duplicate tracking calls.
For known ad slots, prefetch ads during page load and render them when the slot scrolls into view. Use the batch endpoint to minimize round trips.