> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thrad.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tracking & Viewability

> How to track ad impressions and clicks across web and mobile integrations.

> [!WARNING]
>
> **v1 is deprecated.** New integrations should use [v2](https://docs.thrad.ai/api/api-reference/bid-request-v2.md).

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

> [!NOTE]
>
> Sponsored polls use signed `vote_url` and `results_url` fields in addition to normal impression tracking. Poll option clicks submit to `vote_url`; advertiser traffic uses the optional post-vote `cta_url`. See [Sponsored Poll Tracking](https://docs.thrad.ai/api/api-reference/sponsored-poll-tracking.md).

## 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 param | Purpose |
| - | - |
| `view_url` | **DSP impression tracker.** Fire when the ad is viewable. |
| `thrad_view_url` | **SSP impression tracker.** Fire when the ad is viewable (same timing as `view_url`). |

Extract them from the `url` query string and fire them yourself, from the user's device, once the ad is viewable.

## Web / Browser Integration

Parse `view_url` and `thrad_view_url` from the click URL with `URLSearchParams`, then fire both from the page once the ad is viewable, using the `IntersectionObserver` snippet in [Count the impression](https://docs.thrad.ai/api/guides/rendering-v2.md#count-the-impression). The request leaves the user's browser, so the IP and user agent match the bid request.

## Mobile Integration (iOS / Android)

Extract and fire the tracking URLs from the app.

### 1. Extract Tracking URLs from the Click URL

Parse `view_url` and `thrad_view_url` from the `url` query string:

```swift theme={null}
// 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)
}
```

```kotlin theme={null}
// 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:

```swift theme={null}
// 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:

| Header | Description |
| - | - |
| `X-Forwarded-For` | The end-user's original IP address. |
| `User-Agent` | The end-user's original device/browser string. |

> [!WARNING]
>
> View pings missing these headers will be rejected with a **400 Bad Request**. See [Impression Quality Assurance](https://docs.thrad.ai/api/guides/billing-viewability.md#impression-quality-assurance) for details on how impressions are verified.

> [!WARNING]
>
> **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:

```swift theme={null}
// 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.

For sponsored polls, do not use `url` or `cta_url` for option buttons. Option buttons submit the selected `option_id` to `vote_url`; reveal `cta_url` only after voting.

## 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.