Skip to main content
Ad responses include tracking URLs that must be fired to record impressions and clicks. How you fire them depends on your integration type.

Tracking URLs

The click URL (url) contains the tracking URLs as query parameters:
https://ssp.thrads.ai/api/v1/tracking/redirect?token=...&view_url=...&thrad_view_url=...
Query paramPurpose
view_urlDSP impression tracker. Fire when the ad is viewable.
thrad_view_urlSSP impression tracker. Fire when the ad is viewable (same timing as view_url).
For browser integrations, the publisher tag extracts and fires these automatically from the click URL. For mobile, you need to extract them from the url query string and fire them yourself.

Web / Browser Integration

For browser-based integrations (websites, web apps), use the Thrad Publisher Tag — a lightweight script that automatically tracks viewability using IntersectionObserver.

Publisher Tag Setup

Follow the publisher tag quickstart for browser-based tracking
The tag handles:
  • Rendered — element exists in DOM
  • In view — element enters viewport
  • Viewable — 50% visible for at least 1 second (IAB standard)
  • View end — element leaves viewport or page closes
No manual tracking code needed — just wrap your ad in a container with the redirect URL as an <a href> and the tag does the rest.

Mobile Integration (iOS / Android)

Mobile apps don’t run the publisher tag. You need to extract and fire the tracking URLs yourself.

1. Extract Tracking URLs from the Click URL

Parse view_url and thrad_view_url from the url query string:
// iOS (Swift)
func extractTrackingUrls(from clickUrl: String) -> (viewUrl: String?, thradViewUrl: String?) {
    guard let components = URLComponents(string: clickUrl) else { return (nil, nil) }
    let viewUrl = components.queryItems?.first(where: { $0.name == "view_url" })?.value
    let thradViewUrl = components.queryItems?.first(where: { $0.name == "thrad_view_url" })?.value
    return (viewUrl, thradViewUrl)
}
// Android (Kotlin)
fun extractTrackingUrls(clickUrl: String): Pair<String?, String?> {
    val uri = Uri.parse(clickUrl)
    return Pair(uri.getQueryParameter("view_url"), uri.getQueryParameter("thrad_view_url"))
}

2. Fire Impressions on Viewability

When the ad becomes viewable (at least 50% visible for 1 second), fire both URLs. If the app fires directly from the device, the OS networking stack sends the correct headers automatically:
// iOS (Swift) — direct from device (headers sent automatically)
func fireTrackingPixels(viewUrl: String?, thradViewUrl: String?) {
    [viewUrl, thradViewUrl].compactMap { $0 }.forEach { urlString in
        guard let url = URL(string: urlString) else { return }
        URLSession.shared.dataTask(with: url).resume()
    }
}
Every view ping must include the end-user’s original headers:
HeaderDescription
X-Forwarded-ForThe end-user’s original IP address.
User-AgentThe end-user’s original device/browser string.
View pings missing these headers will be rejected with a 400 Bad Request. See Impression Quality Assurance for details on how impressions are verified.
IAB Viewability Standard: Only fire the impression when the ad is at least 50% visible in the viewport for at least 1 second. Firing on render (before the user sees it) inflates impression counts and hurts your analytics accuracy.

3. Handle Clicks

When the user taps the ad, open the url field. It records the click and redirects to the advertiser’s landing page:
// iOS
if let clickUrl = URL(string: bid.url) {
    UIApplication.shared.open(clickUrl)
}
The redirect happens server-side — the user ends up on the advertiser’s site.

Testing in Staging

Staging API keys return test ads with real tracking URLs. Use these to verify your full tracking integration before going live:
  1. Fire view_url and thrad_view_url — both must return 204 No Content. If you get a 400, your request is missing the required X-Forwarded-For or User-Agent headers.
  2. Fire url (click) — should redirect to a test landing page.
Make sure to include the required headers when testing the view pings. A 204 confirms your integration is correctly configured and the impression would be counted in production.