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

# Video Ad Tracking

> Emit standard video ad events from iOS / tvOS using Permutive.shared.track()

Track video ad events to measure ad engagement and enable ad-based cohort qualification.

<CardGroup cols={2}>
  <Card title="Tracking Ad Events" href="#tracking-ad-events" icon="rectangle-ad" />

  <Card title="IMA Integration" href="#google-ima-integration" icon="google" />
</CardGroup>

## Overview

<Warning>
  **iOS / tvOS does not have a native ad tracker.** Unlike Android's `VideoAdTracker`, iOS does not provide a typed API for ad tracking. Emit the standard `VideoAdView`, `VideoAdCompletion`, and `VideoAdClicked` events using `Permutive.shared.track(event:properties:)` with properties nested under an `ad` parent per the [canonical schema](/sdks/ctv/video-tracking#ad-event-properties).
</Warning>

The three schema events for ad tracking are:

| Event                   | When to emit        |
| ----------------------- | ------------------- |
| **`VideoAdView`**       | Ad playback begins  |
| **`VideoAdCompletion`** | Ad finishes playing |
| **`VideoAdClicked`**    | User clicks the ad  |

All three emit `ad.*` properties (and optionally a `video.*` parent for the content the ad ran against). See the schema reference below.

***

## Tracking Ad Events

### VideoAdView

Emit when the ad begins playback:

```swift theme={"dark"}
import Permutive_iOS

func trackAdView(ad: Ad) {
    try? Permutive.shared.track(
        event: "VideoAdView",
        properties: try? EventProperties([
            "ad": [
                "title": ad.title,
                "duration": ad.durationInSeconds,
                "muted": ad.isMuted,
                "campaign_id": ad.campaignId,
                "creative_id": ad.creativeId
            ]
        ])
    )
}
```

### VideoAdCompletion

Emit when the ad finishes:

```swift theme={"dark"}
func trackAdCompletion(ad: Ad) {
    try? Permutive.shared.track(
        event: "VideoAdCompletion",
        properties: try? EventProperties([
            "ad": [
                "title": ad.title,
                "duration": ad.durationInSeconds,
                "campaign_id": ad.campaignId,
                "creative_id": ad.creativeId
            ]
        ])
    )
}
```

### VideoAdClicked

Emit when the user clicks through:

```swift theme={"dark"}
func trackAdClicked(ad: Ad) {
    try? Permutive.shared.track(
        event: "VideoAdClicked",
        properties: try? EventProperties([
            "ad": [
                "title": ad.title,
                "campaign_id": ad.campaignId,
                "creative_id": ad.creativeId
            ]
        ])
    )
}
```

<Info>
  **Skipped or errored ads:** the schema defines only `VideoAdView`, `VideoAdCompletion`, and `VideoAdClicked`. Treat a skip as no completion event (no `VideoAdCompletion`). If you need to capture skip/error signals for your own analytics, use a custom event name and document it in your workspace schema.
</Info>

***

## Combining with Content Tracking

Pause the content video tracker during ad breaks so engagement time isn't counted twice:

```swift theme={"dark"}
class VideoPlayerController {
    private var mediaTracker: (NSObject & MediaTrackerProtocol)?
    private var isPlayingAd = false

    // Content tracking
    func onContentPlay() {
        guard !isPlayingAd else { return }
        mediaTracker?.play()
    }

    func onContentPause() {
        guard !isPlayingAd else { return }
        mediaTracker?.pause()
    }

    // Ad tracking
    func onAdBreakStarted() {
        isPlayingAd = true
        mediaTracker?.pause()  // Pause content engagement during ads
    }

    func onAdBreakEnded() {
        isPlayingAd = false
        // Content resumes via onContentPlay()
    }

    func onAdStarted(_ ad: Ad) {
        try? Permutive.shared.track(
            event: "VideoAdView",
            properties: try? EventProperties([
                "ad": [
                    "title": ad.title,
                    "duration": ad.durationInSeconds,
                    "campaign_id": ad.campaignId,
                    "creative_id": ad.creativeId
                ]
            ])
        )
    }

    func onAdCompleted(_ ad: Ad) {
        try? Permutive.shared.track(
            event: "VideoAdCompletion",
            properties: try? EventProperties([
                "ad": [
                    "title": ad.title,
                    "campaign_id": ad.campaignId,
                    "creative_id": ad.creativeId
                ]
            ])
        )
    }
}
```

***

## Google IMA Integration

Hook `IMAAdsManagerDelegate` events to emit Permutive ad events:

```swift theme={"dark"}
import GoogleInteractiveMediaAds
import Permutive_iOS

class VideoPlayerViewController: UIViewController, IMAAdsManagerDelegate {

    func adsManager(_ adsManager: IMAAdsManager, didReceive event: IMAAdEvent) {
        guard let imaAd = event.ad else { return }

        switch event.type {
        case .STARTED:
            emitAdView(imaAd)
        case .COMPLETE:
            emitAdCompletion(imaAd)
        case .CLICKED:
            emitAdClicked(imaAd)
        default:
            break
        }
    }

    private func emitAdView(_ ad: IMAAd) {
        try? Permutive.shared.track(
            event: "VideoAdView",
            properties: try? EventProperties([
                "ad": [
                    "title": ad.adTitle,
                    "duration": Int(ad.duration),
                    "campaign_id": ad.adId,
                    "creative_id": ad.creativeID ?? ""
                ]
            ])
        )
    }

    private func emitAdCompletion(_ ad: IMAAd) {
        try? Permutive.shared.track(
            event: "VideoAdCompletion",
            properties: try? EventProperties([
                "ad": [
                    "title": ad.adTitle,
                    "campaign_id": ad.adId,
                    "creative_id": ad.creativeID ?? ""
                ]
            ])
        )
    }

    private func emitAdClicked(_ ad: IMAAd) {
        try? Permutive.shared.track(
            event: "VideoAdClicked",
            properties: try? EventProperties([
                "ad": [
                    "title": ad.adTitle,
                    "campaign_id": ad.adId,
                    "creative_id": ad.creativeID ?? ""
                ]
            ])
        )
    }
}
```

<Info>
  IMA's `adId` is the line-item / ad reference. Map it to whichever Permutive schema field reflects your taxonomy (`campaign_id` shown here, but use `creative_id` if that's a closer match).
</Info>

***

## Schema Reference

The canonical schema for ad events is defined in [CTV Video Tracking](/sdks/ctv/video-tracking#ad-event-properties). Summary:

| Property         | Type   | Description              |
| ---------------- | ------ | ------------------------ |
| `ad.title`       | String | Ad title                 |
| `ad.duration`    | Int    | Ad duration in seconds   |
| `ad.muted`       | Bool   | Whether the ad was muted |
| `ad.campaign_id` | String | Campaign identifier      |
| `ad.creative_id` | String | Creative identifier      |

For `VideoAdCompletion`, the SDK does not auto-aggregate engagement on iOS (no native ad tracker means no `aggregations.VideoAdEngagement.*` enrichment) — emit only the `ad.*` properties.

<Warning>
  Ensure property names match your Permutive workspace schema. Mismatched properties are rejected.
</Warning>

***

## tvOS Considerations

<Info>
  **tvOS Note:** Ad tracking on tvOS uses the same `Permutive.shared.track(event:properties:)` API. Use it from your TVMLKit or UIKit video player.
</Info>

***

## Best Practices

<Tabs>
  <Tab title="Do">
    * Emit `VideoAdView` only when the ad is actually visible / playing
    * Nest all ad properties under an `ad` parent (e.g., `ad.title`, not flat `ad_title`)
    * Use the canonical event names — `VideoAdView`, `VideoAdCompletion`, `VideoAdClicked`
    * Pause the content `MediaTracker` during ad breaks so content engagement isn't inflated
    * Pass duration in seconds (Int), not milliseconds
  </Tab>

  <Tab title="Don't">
    * Don't invent event names like `VideoAdImpression` or `VideoAdSkipped` unless they're configured in your workspace schema
    * Don't flatten ad properties at the top level (the schema requires `ad.*` nesting)
    * Don't track an ad impression before the ad actually starts playing
    * Don't include PII in ad properties
  </Tab>
</Tabs>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Ad events not appearing">
    **Problem:** Ad events do not show in the Permutive dashboard.

    **Solutions:**

    * Confirm `Permutive.configure(...)` ran before `track(...)` is called
    * Verify event names match: `VideoAdView`, `VideoAdCompletion`, `VideoAdClicked`
    * Check that `ad.*` property nesting matches your workspace schema
    * Enable debug logging and watch for schema validation errors
  </Accordion>

  <Accordion title="Events rejected for schema mismatch">
    **Problem:** Logs show events being rejected.

    **Solutions:**

    * Ensure all properties are nested under `ad` (not at the top level)
    * Check property types — `duration` must be Int, `muted` must be Bool
    * Verify the event name spelling exactly matches the schema
  </Accordion>

  <Accordion title="Targeting not applied to ad requests">
    **Problem:** Permutive targeting does not appear on GAM ad requests.

    **Solutions:**

    * Call `googleCustomTargeting(adTargetable:)` before the ad request
    * Pass an active `MediaTracker` or `PageTracker` as `adTargetable` so targeting reflects the current session
    * Confirm the targeting dictionary is applied to the `GAMRequest`
  </Accordion>
</AccordionGroup>

***

## Related Documentation

<CardGroup cols={2}>
  <Card title="Video Tracking" icon="video" href="/sdks/mobile/ios/features/video-tracking">
    Track video content
  </Card>

  <Card title="CTV Video Schema" icon="list" href="/sdks/ctv/video-tracking">
    Canonical event and property reference
  </Card>

  <Card title="Google Ad Manager" icon="google" href="/sdks/mobile/ios/integrations/google-ad-manager">
    GAM integration
  </Card>

  <Card title="Event Tracking" icon="bolt" href="/sdks/mobile/ios/features/event-tracking">
    Custom event tracking
  </Card>
</CardGroup>
