Brand Store (Mobile Apps)

Native iOS/Android integration for Brand Store format without JS SDK

Brand Store (Mobile Apps)

No SDK Required: This guide shows direct HTTP API integration for native mobile apps. For web integration with JavaScript SDK, see Brand Store (Web).

A button on the OOP (product detail page) displayed below the product that redirects users directly to the brand's official store page.

What Is Brand Store?

Brand Store is a sponsored button that appears on product detail pages in your mobile app. When a user taps it, they are redirected to the brand's (manufacturer's) official store where they can purchase the product directly.

Key concept: The ad server tells you IF a campaign is active for a given brand. Your app decides WHETHER to display the button based on your own business logic.

How It Works

What the Ad Server Provides

Based on the manufacturer_id you pass, the ad server checks if there is an active campaign for this brand (manufacturer). If yes, it returns:

  • Confirmation that a sponsored ad exists
  • Click tracking URL for revenue attribution
  • Brand logo URL in the image field of the creative
  • Impression and viewability tracking endpoints

Important: The ad server provides tracking information and the brand logo URL. Price and store URL still come from YOUR app's data.

What Your App Must Check

Before displaying the button, your app should verify:

  1. Brand has a store page - Does this manufacturer have a brand shop in your system?
  2. Product offer exists - Does the brand's store have an offer for the current product?
  3. Price is competitive - Is the brand's offer cheaper than (or equal to) the current best price?

Display logic: Only show the Brand Store button if all conditions are met: active campaign (from ad server) + brand shop exists + competitive offer (from your app's data).

Data Sources

SourceData
Ad ServerWhether campaign is active, click tracking URL, impression/viewability tracking, brand logo URL
Your AppBrand shop URL, price, shop name, offer availability

Data Flow

  1. Pass manufacturer_id to ad server via bidder request
  2. Ad server returns tracking info IF campaign is active for this brand
  3. Your app checks: Does brand have a store? Is offer available and competitive?
  4. If all conditions met → render button with your app data + ad server tracking

Live Demo

See the working implementation:


Integration Flow Overview

sequenceDiagram
    participant App as Mobile App
    participant ShopAPI as Shop API/Database
    participant Bidder as Ring DAS
    participant Events as Events Tracking API

    App->>Bidder: 1. Fetch Ad (GET /bid)
    Bidder-->>App: Response with gctx, lctx, tracking URLs

    Note over App: 2. Parse response

    App->>ShopAPI: 3. Fetch brand store data
    ShopAPI-->>App: Brand shop URL, price, availability

    Note over App: 4. Verify conditions:<br/>- Brand shop exists?<br/>- Offer available?<br/>- Price competitive?

    alt All conditions met
        Note over App: 5. Render button with shop data<br/>(Logo from ad response)
        App->>Events: 6. Fire Impression (/ems)
        Note over App: Wait for viewability (50% visible, 1s)
        App->>Events: 7. Fire Active View (/av)

        Note over App: User taps ad
        App->>Events: 8. Fire Click Tracking
        Note over App: Open shop URL
    else Conditions not met
        Note over App: Hide ad slot
    end

Optional Request Parameters

The following optional parameters can be passed to enhance user identification and page view tracking. For full field reference see API Parameters Reference.

{
  "user": {
    "eids": [{
      "source": "your-domain.com",
      "inserter": "your-domain.com",
      "uids": [
        {
          "id": "your-session-id",
          "atype": 500,
          "ext": { "id_type": "session", "consent_required": false }
        },
        {
          "id": "your-tracking-id",
          "atype": 1,
          "ext": { "id_type": "tracking", "consent_required": true }
        }
      ]
    }]
  },
  "ext": {
    "keyvalues": {
      "IP": "202603041502158687149647",
      "IV": "202603041502158687149647",
      "TABID": "tab-7f3e2a"
    }
  }
}

Step 1: Fetch Ad from Bidder

Make a GET request to the Ring DAS bidder endpoint.

Request Configuration

ParameterDescription
BIDDER_URLBidder endpoint URL - provided by your Ring DAS account manager
NETWORK_IDYour Ring DAS network ID (without EA- prefix)
tmaxRequest timeout in milliseconds
ext.srcSet to app for requests originating from a mobile application
ext.is_non_prebid_requestSet to true to receive the response in the correct server-to-server format (not Prebid.js format)

Placement Parameters

These parameters define WHERE the ad appears and WHICH format to serve.

ParameterDescription
site.idYour site identifier in Ring DAS
site.ext.areaPage type context: listing, search, product_card, main, other
imp[].tagidFixed value product-button for this format

Targeting Parameters

ParameterDescription
ext.keyvalues.manufacturer_idManufacturer identifier for targeting
ext.keyvalues.main_category_idMain product category ID — primary category for ad matching and reporting
ext.keyvalues.page_product_idProduct ID currently viewed — used for reporting purposes
ext.keyvalues.page_product_nameProduct name currently viewed — used for reporting purposes
ext.keyvalues.IPPage View ID — constant identifier for the current page load. See API Parameters Reference.
ext.keyvalues.IVPage View Unique ID — changes on every SPA view transition. See API Parameters Reference.
ext.keyvalues.TABIDBrowser Tab Identifier — stable ID per browser tab session. See API Parameters Reference.

Consent & Privacy Parameters

ParameterDescription
user.ext.npaConsent flag: false = consent given, true = no consent
regs.gdprGDPR applies flag: 1 = GDPR applies, 0 = does not apply
regs.ext.dsaSet to 1 to request DSA transparency info in response

User Identifier

ParameterDescription
user.eidsOpenRTB Extended Identity Array — pass your session/tracking identifiers with GDPR consent metadata. See API Parameters Reference.
user.ext.ids.luUser identifier from ea_uuid cookie (see User Identifier below)

Examples:

// iOS - Swift
import Foundation

let networkId = "7012768"  // your Ring DAS network ID
let bidderUrl = "https://das.idealo.com/\(networkId)/bid"

let requestBody: [String: Any] = [
    "id": UUID().uuidString,
    "imp": [[
        "id": "imp-1",
        "tagid": "product-button",
        "secure": 1,
        "native": ["request": "{}"]
    ]],
    "site": [
        "id": "demo_page",  // your site ID
        "ext": ["area": "product_card"]
    ],
    "user": [
        "ext": [
            "npa": false,  // false = consent given
            "ids": ["lu": generateUserId(), "sid": getSessionId()]  // sid: your own persistent identifier (alternative to lu)
        ]
    ],
    "ext": [
        "network": networkId,
        "keyvalues": [
            "manufacturer_id": "12345678",
            "main_category_id": 101,
            "page_product_id": "987654",
            "page_product_name": "Example Product Name"
        ],
        "is_non_prebid_request": true,
        "src": "app"
    ],
    "regs": [
        "gdpr": 1,
        "ext": ["dsa": 1]
    ],
    "tmax": 1000
]

// Encode and send as GET request
let jsonData = try! JSONSerialization.data(withJSONObject: requestBody)
let jsonString = String(data: jsonData, encoding: .utf8)!
let encoded = jsonString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let url = URL(string: "\(bidderUrl)?data=\(encoded)")!

URLSession.shared.dataTask(with: url) { data, response, error in
    guard let httpResponse = response as? HTTPURLResponse else { return }

    // Handle 204 No Content (no ad available)
    if httpResponse.statusCode == 204 {
        print("No ad available")
        return
    }

    // Parse response...
}.resume()
// Android - Kotlin
import okhttp3.*
import org.json.JSONArray
import org.json.JSONObject
import java.net.URLEncoder

val networkId = "7012768"  // your Ring DAS network ID
val bidderUrl = "https://das.idealo.com/$networkId/bid"

val requestBody = JSONObject().apply {
    put("id", java.util.UUID.randomUUID().toString())
    put("imp", JSONArray().put(JSONObject().apply {
        put("id", "imp-1")
        put("tagid", "product-button")
        put("secure", 1)
        put("native", JSONObject().put("request", "{}"))
    }))
    put("site", JSONObject().apply {
        put("id", "demo_page")  // your site ID
        put("ext", JSONObject().put("area", "product_card"))
    })
    put("user", JSONObject().apply {
        put("ext", JSONObject().apply {
            put("npa", false)  // false = consent given
            put("ids", JSONObject().put("lu", generateUserId()).put("sid", getSessionId()))  // sid: your own persistent identifier (alternative to lu)
        })
    })
    put("ext", JSONObject().apply {
        put("network", networkId)
        put("keyvalues", JSONObject().apply {
            put("manufacturer_id", "12345678")
            put("main_category_id", 101)
            put("page_product_id", "987654")
            put("page_product_name", "Example Product Name")
        })
        put("is_non_prebid_request", true)
        put("src", "app")
    })
    put("regs", JSONObject().apply {
        put("gdpr", 1)
        put("ext", JSONObject().put("dsa", 1))
    })
    put("tmax", 1000)
}

// Encode and send as GET request
val encoded = URLEncoder.encode(requestBody.toString(), "UTF-8")
val request = Request.Builder().url("$bidderUrl?data=$encoded").build()

client.newCall(request).enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {
        // Handle 204 No Content (no ad available)
        if (response.code == 204) {
            println("No ad available")
            return
        }

        // Parse response...
    }
    override fun onFailure(call: Call, e: IOException) { }
})

No ad available: When no ad matches the request criteria, the server returns HTTP 204 No Content with an empty response body. Always check the status code before parsing JSON.


Step 2: Parse Response

Extract tracking data from the bid response.

FieldDescription
gctxGlobal context - required for impression/viewability tracking
lctxLocal context - required for impression/viewability tracking
meta.adclickClick tracking URL — fire as a pixel on user tap
fields.imageBrand logo URL — use directly to display the brand logo
bid.ext.dsaDSA transparency info (if requested)

Examples:

// iOS - Swift
guard let data = data,
      let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
      let seatbid = json["seatbid"] as? [[String: Any]],
      let bid = seatbid.first?["bid"] as? [[String: Any]],
      let firstBid = bid.first,
      let admString = firstBid["adm"] as? String,
      let admData = admString.data(using: .utf8),
      let adm = try? JSONSerialization.jsonObject(with: admData) as? [String: Any] else {
    return
}

// Extract tracking data
let gctx = adm["gctx"] as? String
let lctx = adm["lctx"] as? String

// Click tracking URL
let clickUrl = (adm["meta"] as? [String: Any])?["adclick"] as? String

if let fields = adm["fields"] as? [String: Any] {
    // Brand logo from AD SERVER
    let logoUrl = fields["image"] as? String
}

// DSA info (optional)
let dsaInfo = (firstBid["ext"] as? [String: Any])?["dsa"] as? [String: Any]
// Android - Kotlin
response.body?.string()?.let { body ->
    val json = JSONObject(body)
    val seatbid = json.optJSONArray("seatbid") ?: return
    val bid = seatbid.optJSONObject(0)?.optJSONArray("bid")?.optJSONObject(0) ?: return
    val adm = JSONObject(bid.getString("adm"))

    // Extract tracking data
    val gctx = adm.optString("gctx")
    val lctx = adm.optString("lctx")

    // Click tracking URL
    val clickUrl = adm.optJSONObject("meta")?.optString("adclick", null)

    // Brand logo from AD SERVER
    val logoUrl = adm.optJSONObject("fields")?.optString("image", null)

    // DSA info (optional)
    val dsaInfo = bid.optJSONObject("ext")?.optJSONObject("dsa")
}

Step 3: Render with Shop Data

Use YOUR app's product data to render the ad. The ad server does NOT return price or store URL — your app provides those. The brand logo comes from the ad response (fields.image).

// iOS - Swift
// YOUR shop data (price and URL) - brand logo comes from ad server!
struct ShopData {
    let productUrl = "https://apple.com/store/iphone"
    let shopName = "Apple"
    let price = "€899.00"
    // Note: brand logo comes from ad response (fields.image)
}

// Render button in your app's UI
// Attach click handler that:
// 1. Fires click tracking pixel
// 2. Opens shop URL
// Android - Kotlin
// YOUR shop data (price and URL) - brand logo comes from ad server!
data class ShopData(
    val productUrl: String = "https://apple.com/store/iphone",
    val shopName: String = "Apple",
    val price: String = "€899.00"
    // Note: brand logo comes from ad response (fields.image)
)

// Render button in your app's UI
// Attach click handler that:
// 1. Fires click tracking pixel
// 2. Opens shop URL

Step 4: Fire Impression Event

Fire this event when the ad is loaded and ready to display.

Endpoint: GET https://das.idealo.com/{NETWORK_ID}/v1/events-processor/ems

ParameterDescription
eventDataURL-encoded JSON with gctx and ems array
gctxGlobal context from bid response
lctxLocal context from bid response
is_measurableSet to true to indicate viewability can be measured

Examples:

// iOS - Swift
func fireImpression(gctx: String, lctx: String, networkId: String) {
    let eventData: [String: Any] = [
        "gctx": gctx,
        "ems": [[
            "lctx": lctx,
            "is_measurable": true
        ]]
    ]

    guard let jsonData = try? JSONSerialization.data(withJSONObject: eventData),
          let jsonString = String(data: jsonData, encoding: .utf8),
          let encoded = jsonString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
          let url = URL(string: "https://das.idealo.com/\(networkId)/v1/events-processor/ems?eventData=\(encoded)") else {
        return
    }

    URLSession.shared.dataTask(with: url).resume()
}
// Android - Kotlin
fun fireImpression(gctx: String, lctx: String, networkId: String) {
    val eventData = JSONObject().apply {
        put("gctx", gctx)
        put("ems", JSONArray().put(JSONObject().apply {
            put("lctx", lctx)
            put("is_measurable", true)
        }))
    }

    val encoded = URLEncoder.encode(eventData.toString(), "UTF-8")
    val url = "https://das.idealo.com/$networkId/v1/events-processor/ems?eventData=$encoded"

    client.newCall(Request.Builder().url(url).build()).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {}
        override fun onResponse(call: Call, response: Response) {}
    })
}

Step 5: Fire Active View Event

🚧

Your App Must Implement Viewability Measurement!

Fire /av only when these conditions are met (MRC/IAB Standard):

  • At least 50% of ad pixels are visible on screen
  • Ad has been visible for at least 1 continuous second

Use platform APIs:

  • iOS: UIView visibility checks + CADisplayLink or Timer for duration
  • Android: View.getGlobalVisibleRect() + Handler for duration tracking

Endpoint: GET https://das.idealo.com/{NETWORK_ID}/v1/events-processor/av

Examples:

// iOS - Swift
func fireActiveView(gctx: String, lctx: String, networkId: String) {
    let eventData: [String: Any] = [
        "gctx": gctx,
        "ems": [[
            "lctx": lctx
        ]]
    ]

    guard let jsonData = try? JSONSerialization.data(withJSONObject: eventData),
          let jsonString = String(data: jsonData, encoding: .utf8),
          let encoded = jsonString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
          let url = URL(string: "https://das.idealo.com/\(networkId)/v1/events-processor/av?eventData=\(encoded)") else {
        return
    }

    URLSession.shared.dataTask(with: url).resume()
}

// Example viewability implementation
class ViewabilityTracker {
    private var timer: Timer?
    private var visibleStartTime: Date?
    private let requiredDuration: TimeInterval = 1.0
    private let requiredVisibility: CGFloat = 0.5

    func startTracking(adView: UIView, onViewable: @escaping () -> Void) {
        timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
            guard let self = self else { return }

            if self.isViewVisible(adView, threshold: self.requiredVisibility) {
                if self.visibleStartTime == nil {
                    self.visibleStartTime = Date()
                } else if Date().timeIntervalSince(self.visibleStartTime!) >= self.requiredDuration {
                    self.timer?.invalidate()
                    onViewable()
                }
            } else {
                self.visibleStartTime = nil  // Reset if visibility lost
            }
        }
    }

    private func isViewVisible(_ view: UIView, threshold: CGFloat) -> Bool {
        guard let window = view.window else { return false }
        let viewFrame = view.convert(view.bounds, to: window)
        let intersection = viewFrame.intersection(window.bounds)
        let visibleArea = intersection.width * intersection.height
        let totalArea = viewFrame.width * viewFrame.height
        return visibleArea / totalArea >= threshold
    }
}
// Android - Kotlin
fun fireActiveView(gctx: String, lctx: String, networkId: String) {
    val eventData = JSONObject().apply {
        put("gctx", gctx)
        put("ems", JSONArray().put(JSONObject().put("lctx", lctx)))
    }

    val encoded = URLEncoder.encode(eventData.toString(), "UTF-8")
    val url = "https://das.idealo.com/$networkId/v1/events-processor/av?eventData=$encoded"

    client.newCall(Request.Builder().url(url).build()).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {}
        override fun onResponse(call: Call, response: Response) {}
    })
}

// Example viewability implementation
class ViewabilityTracker(
    private val adView: View,
    private val onViewable: () -> Unit
) {
    private val handler = Handler(Looper.getMainLooper())
    private var visibleStartTime: Long? = null
    private val requiredDurationMs = 1000L
    private val requiredVisibility = 0.5f

    private val checkRunnable = object : Runnable {
        override fun run() {
            if (isViewVisible(adView, requiredVisibility)) {
                if (visibleStartTime == null) {
                    visibleStartTime = System.currentTimeMillis()
                } else if (System.currentTimeMillis() - visibleStartTime!! >= requiredDurationMs) {
                    onViewable()
                    return  // Stop checking
                }
            } else {
                visibleStartTime = null  // Reset if visibility lost
            }
            handler.postDelayed(this, 100)
        }
    }

    fun startTracking() {
        handler.post(checkRunnable)
    }

    fun stopTracking() {
        handler.removeCallbacks(checkRunnable)
    }

    private fun isViewVisible(view: View, threshold: Float): Boolean {
        val visibleRect = Rect()
        if (!view.getGlobalVisibleRect(visibleRect)) return false

        val visibleArea = visibleRect.width() * visibleRect.height()
        val totalArea = view.width * view.height
        return visibleArea.toFloat() / totalArea >= threshold
    }
}

Step 6: Fire Click Tracking

Fire the click tracking pixel when the user taps the ad, then open the shop URL.

Examples:

// iOS - Swift
func handleAdClick(clickUrl: String, shopUrl: String) {
    // 1. Fire click tracking pixel (non-blocking)
    if let url = URL(string: clickUrl) {
        URLSession.shared.dataTask(with: url).resume()
    }

    // 2. Open shop URL
    if let url = URL(string: shopUrl) {
        UIApplication.shared.open(url)
    }
}
// Android - Kotlin
fun handleAdClick(clickUrl: String, shopUrl: String, context: Context) {
    // 1. Fire click tracking pixel (non-blocking)
    client.newCall(Request.Builder().url(clickUrl).build()).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {}
        override fun onResponse(call: Call, response: Response) {}
    })

    // 2. Open shop URL
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(shopUrl))
    context.startActivity(intent)
}

User Identifier

A user identifier enables frequency capping, personalization, and other targeting features.

ParameterDescription
user.eidsOpenRTB Extended Identity Array — pass user identifiers with per-ID GDPR consent metadata. Supports session-level (atype: 500, no consent required) and device-level (atype: 1, consent required) identifiers. See API Parameters Reference for full field reference.
user.ext.ids.luLocal user ID from the ea_uuid cookie (set by Ring DAS). Must follow the alphanumeric format (e.g., 202502051230001234567890).

User identifiers:

  • user.eids — Pass your own session and/or tracking identifiers with GDPR consent metadata. Provide your session ID as atype: 500 (no consent required) and your device/tracking ID as atype: 1 (consent required).
  • user.ext.ids.lu — The value from the ea_uuid cookie set by Ring DAS. Used as Ring DAS's internal session identifier.

Both can be sent simultaneously. See API Parameters Reference for the full user.eids field reference.

🚧

Critical: Persist the User Identifier!

Store this identifier persistently and reuse the same value across:

  • All ad requests within a session
  • All sessions for the same user

Why this matters:

  • Frequency capping - Limits how often a user sees the same ad
  • Budget pacing - Distributes advertiser spend evenly across users
  • Attribution - Tracks conversions back to ad impressions
  • Personalization - Enables relevant ad targeting

Generating a new lu on every app launch breaks these features and reduces ad effectiveness.


Caching Considerations

🚧

Important: Improper caching can significantly reduce ad revenue and break tracking.

Do NOT cache:

  • Tracking pixels - Caching impression/click pixels prevents accurate counting and drastically reduces revenue
  • API responses - Caching bid responses prevents real-time optimization, budget pacing, and frequency capping

Safe to cache:

  • Static assets (images, fonts from your app)
  • Product data from your own database

Complete Integration Example

Here's a complete Swift class showing the full integration flow:

// iOS - Swift - Complete Example
import Foundation
import UIKit

class BrandStoreAdManager {
    private let networkId: String
    private let bidderUrl: String
    private let client = URLSession.shared

    // Parsed ad data
    private var gctx: String?
    private var lctx: String?
    private var clickUrl: String?
    private var logoUrl: String?
    private var userId: String
    private var sessionId: String?  // your own persistent identifier (alternative to lu)

    init(networkId: String) {
        self.networkId = networkId
        self.bidderUrl = "https://das.idealo.com/\(networkId)/bid"
        self.userId = Self.generateUserId()
    }

    // MARK: - Public API

    func fetchAd(siteId: String, manufacturerId: String, completion: @escaping (Bool) -> Void) {
        let requestBody = buildRequest(siteId: siteId, manufacturerId: manufacturerId)

        guard let url = buildRequestURL(requestBody) else {
            completion(false)
            return
        }

        client.dataTask(with: url) { [weak self] data, response, _ in
            guard let httpResponse = response as? HTTPURLResponse else {
                completion(false)
                return
            }

            if httpResponse.statusCode == 204 {
                completion(false)
                return
            }

            guard let data = data else {
                completion(false)
                return
            }

            self?.parseResponse(data)
            completion(self?.gctx != nil)
        }.resume()
    }

    func fireImpression() {
        guard let gctx = gctx, let lctx = lctx else { return }
        fireEvent(endpoint: "ems", gctx: gctx, lctx: lctx, isMeasurable: true)
    }

    func fireActiveView() {
        guard let gctx = gctx, let lctx = lctx else { return }
        fireEvent(endpoint: "av", gctx: gctx, lctx: lctx, isMeasurable: false)
    }

    func handleClick(shopUrl: String) {
        // Fire click tracking
        if let clickUrl = clickUrl, let url = URL(string: clickUrl) {
            client.dataTask(with: url).resume()
        }

        // Open shop URL
        if let url = URL(string: shopUrl) {
            DispatchQueue.main.async {
                UIApplication.shared.open(url)
            }
        }
    }

    // MARK: - Private Helpers

    private func buildRequest(siteId: String, manufacturerId: String) -> [String: Any] {
        return [
            "id": UUID().uuidString,
            "imp": [["id": "imp-1", "tagid": "product-button", "secure": 1, "native": ["request": "{}"]]],
            "site": ["id": siteId, "ext": ["area": "product_card"]],
            "user": ["ext": ["npa": false, "ids": ["lu": userId, "sid": sessionId]]],
            "ext": ["network": networkId, "keyvalues": ["manufacturer_id": manufacturerId, "main_category_id": 101, "page_product_id": "987654", "page_product_name": "Example Product Name"], "is_non_prebid_request": true, "src": "app"],
            "regs": ["gdpr": 1, "ext": ["dsa": 1]],
            "tmax": 1000
        ]
    }

    private func buildRequestURL(_ body: [String: Any]) -> URL? {
        guard let jsonData = try? JSONSerialization.data(withJSONObject: body),
              let jsonString = String(data: jsonData, encoding: .utf8),
              let encoded = jsonString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
            return nil
        }
        return URL(string: "\(bidderUrl)?data=\(encoded)")
    }

    private func parseResponse(_ data: Data) {
        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let seatbid = json["seatbid"] as? [[String: Any]],
              let bid = seatbid.first?["bid"] as? [[String: Any]],
              let firstBid = bid.first,
              let admString = firstBid["adm"] as? String,
              let admData = admString.data(using: .utf8),
              let adm = try? JSONSerialization.jsonObject(with: admData) as? [String: Any] else {
            return
        }

        gctx = adm["gctx"] as? String
        lctx = adm["lctx"] as? String

        // Click tracking URL
        clickUrl = (adm["meta"] as? [String: Any])?["adclick"] as? String

        // Brand logo from AD SERVER
        if let fields = adm["fields"] as? [String: Any],
           let image = fields["image"] as? String {
            logoUrl = image
        }
    }

    private func fireEvent(endpoint: String, gctx: String, lctx: String, isMeasurable: Bool) {
        var emsItem: [String: Any] = ["lctx": lctx]
        if isMeasurable {
            emsItem["is_measurable"] = true
        }

        let eventData: [String: Any] = ["gctx": gctx, "ems": [emsItem]]

        guard let jsonData = try? JSONSerialization.data(withJSONObject: eventData),
              let jsonString = String(data: jsonData, encoding: .utf8),
              let encoded = jsonString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
              let url = URL(string: "https://das.idealo.com/\(networkId)/v1/events-processor/\(endpoint)?eventData=\(encoded)") else {
            return
        }

        client.dataTask(with: url).resume()
    }

    private static func generateUserId() -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyyMMddHHmmss"
        let timestamp = formatter.string(from: Date())
        let random = String(format: "%010d", Int.random(in: 0..<10_000_000_000))
        return timestamp + random
    }
}

Related


What’s Next

Learn about the web-based Brand Store integration