Prooflytics + Apple Search Ads: атрибуция от установки приложения до закрытой B2B-сделки

Apple Search Ads - рекламный канал App Store для привлечения пользователей через поиск. Для B2B SaaS с мобильным приложением это работает так: пользователь ищет «crm для продаж» в App Store, видит вашу рекламу, устанавливает приложение и через 2-3 недели становится платящим клиентом. Без атрибуции вы знаете только cost per install. С Prooflytics - стоимость закрытой B2B-сделки из App Store кампании с разбивкой по keywords и adGroups.

B2B-маркетологи недооценивают Apple Search Ads по простой причине: стандартные инструменты аналитики показывают только install rate и conversion to trial. Цепочка «keyword -> install -> trial -> qualified lead -> closed deal» обычно обрывается на стыке мобильного приложения и CRM. Именно этот разрыв закрывает интеграция Prooflytics с Apple Search Ads.

В этой статье разберём технологию атрибуции AdServices, как передать attributionToken в Prooflytics и увидеть стоимость закрытой сделки из каждой App Store кампании.

Почему стандартные инструменты не дают полной картины

Apple Search Ads предоставляет встроенную аналитику: impressions, taps (клики), installs, TTR, CR. На уровне кампании и keyword группы. Но здесь данные заканчиваются - дальше в CRM они не попадают.

После iOS 14.5 (ATT фреймворк, 2021) отслеживание через IDFA требует явного согласия пользователя. Большинство пользователей отказывают. Это означает что сторонние MMP (AppsFlyer, Adjust, Branch) работают с ограниченными данными.

Apple решила эту проблему собственным инструментом: AdServices Attribution API - privacy-preserving атрибуция, которая не требует IDFA. Работает через cryptographically-signed attributionToken, генерируемый на устройстве.

AdServices attribution - Apple-native механизм атрибуции: приложение запрашивает токен на устройстве сразу после установки, отправляет его на ваш сервер, сервер обменивает токен на данные кампании через Apple API. Токен не передаёт личные данные пользователя - только мета-данные кампании.

Техническая архитектура

iOS App
  -> attributionToken() [AdServices framework]
  -> POST /prooflytics/apple-attribution {token, user_id}

Prooflytics Backend
  -> POST https://api-adservices.apple.com/api/v1/ {token}
  <- {campaignId, adGroupId, keywordId, clickDate, countryOrRegion}
  -> Сохранить attribution record (user_id -> campaign mapping)

CRM (HubSpot / Kommo)
  -> Lead/Deal created (email или user_id)
  <- Prooflytics находит attribution record по email
  -> Записать источник: Apple Search Ads, campaignId, keyword

Когда сделка закрыта:
  <- Prooflytics получает событие closed_won
  -> Рассчитать CAC: campaign spend / closed deals
  -> Показать в дашборде: keywords с наименьшим CAC

Реализация: iOS-часть

В iOS-приложении (Swift) при первом запуске запрашиваем attribution token:

import AdServices

func fetchAndSendAttributionToken(userId: String) {
    guard #available(iOS 14.3, *) else { return }

    do {
        // Получаем токен атрибуции. TTL: 24 часа
        let token = try AAAttribution.attributionToken()

        // Отправляем в Prooflytics немедленно
        sendToProoflytics(token: token, userId: userId)
    } catch {
        // Токен недоступен если пользователь не пришёл из рекламы
        // Это нормально - просто нет данных атрибуции
        print("No attribution token: \(error)")
    }
}

func sendToProoflytics(token: String, userId: String) {
    let url = URL(string: "https://api.prooflytics.io/v1/attribution/apple")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue("Bearer YOUR_PROOFLYTICS_API_KEY", forHTTPHeaderField: "Authorization")

    let body: [String: Any] = [
        "token":   token,
        "user_id": userId,
        "app_id":  "your.bundle.identifier"
    ]
    request.httpBody = try? JSONSerialization.data(withJSONObject: body)

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

Вызывать fetchAndSendAttributionToken при первом запуске приложения после регистрации/входа - когда userId уже известен.

Реализация: серверная часть (Prooflytics)

import requests, time
from flask import Flask, request, jsonify

app = Flask(__name__)
APPLE_ATTRIBUTION_API = "https://api-adservices.apple.com/api/v1/"

@app.route("/v1/attribution/apple", methods=["POST"])
def receive_apple_attribution():
    data    = request.json
    token   = data.get("token", "")
    user_id = data.get("user_id", "")
    app_id  = data.get("app_id", "")

    if not token or not user_id:
        return jsonify({"error": "missing fields"}), 400

    # Обменять токен на данные кампании у Apple
    attribution = exchange_token(token)
    if not attribution:
        return jsonify({"status": "no_attribution"}), 200

    # Сохранить attribution record в БД
    save_attribution(user_id, app_id, attribution)
    return jsonify({"status": "ok"}), 200

def exchange_token(token: str) -> dict | None:
    """Exchange attribution token with Apple API. Token TTL: 24 hours."""
    try:
        r = requests.post(
            APPLE_ATTRIBUTION_API,
            json={"token": token},
            headers={"Content-Type": "application/json"},
            timeout=5
        )
        if r.status_code == 200:
            data = r.json()
            # attribution = None если пользователь не пришёл из рекламы
            if data.get("attribution") is True:
                return data
        return None
    except Exception:
        return None

def save_attribution(user_id: str, app_id: str, data: dict):
    """
    Пример данных attribution payload (с согласием ATT):
    {
      "attribution": true,
      "orgId": 12345,
      "campaignId": 678901,
      "adGroupId": 234567,
      "keywordId": 890123,
      "adId": 456789,
      "countryOrRegion": "US",
      "conversionType": "download",
      "clickDate": "2026-05-20T14:23:00Z"
    }
    """
    # Сохраняем в Prooflytics attribution store
    # В реальной реализации - INSERT в PostgreSQL таблицу apple_attribution
    record = {
        "user_id":        user_id,
        "app_id":         app_id,
        "campaign_id":    data.get("campaignId"),
        "ad_group_id":    data.get("adGroupId"),
        "keyword_id":     data.get("keywordId"),
        "country":        data.get("countryOrRegion"),
        "click_date":     data.get("clickDate"),
        "conversion_type": data.get("conversionType"),
        "org_id":         data.get("orgId"),
        "received_at":    int(time.time()),
    }
    db.insert("apple_attribution", record)

Привязка attribution к CRM-сделке

Когда лид из мобильного приложения регистрируется в CRM (HubSpot или Kommo), Prooflytics ищет attribution record по user_id или email:

def enrich_lead_with_attribution(email: str, user_id: str) -> dict | None:
    """Find Apple Search Ads attribution for this user."""
    record = db.query_one(
        "SELECT * FROM apple_attribution WHERE user_id = %s ORDER BY received_at DESC LIMIT 1",
        (user_id,)
    )
    if not record:
        return None

    # Получаем название campaign и keyword из Apple Ads API
    campaign_name = get_campaign_name(record["org_id"], record["campaign_id"])
    keyword_text  = get_keyword_text(record["org_id"], record["keyword_id"])

    return {
        "source":        "Apple Search Ads",
        "campaign_id":   record["campaign_id"],
        "campaign_name": campaign_name,
        "keyword":       keyword_text,
        "click_date":    record["click_date"],
        "country":       record["country"],
    }

Эти данные записываются в CRM как UTM-параметры или custom source properties - точно так же, как для Google Ads (через gclid) или Meta (через fbclid).

Что видит маркетолог в Prooflytics

После настройки в дашборде Prooflytics появляется:

  • CAC по campaigns: «Кампания iPhone Users - US: $847 за закрытую сделку»
  • CAC по keywords: «b2b crm app keyword: 3 сделки за месяц, $612 CAC»
  • Time to deal: среднее время от установки до закрытия (например, 34 дня)
  • Funnel by source: install -> trial -> qualified lead -> closed deal - по каждому ad group

Вместо «мы потратили $5000 на App Store и получили 200 установок» маркетолог видит «из 200 установок 12 дошли до paid plan, средний CAC $416, лучший keyword по CAC - “field service app”».

Реальный кейс

B2B SaaS для полевого сервиса: iOS-приложение, продажи enterprise через SDR-team. Apple Search Ads использовался как канал привлечения, но его эффективность оценивалась только по installs и trials.

После интеграции с Prooflytics:

  • 4 месяца данных: $18 000 потрачено на Apple Search Ads, 840 установок
  • Из 840 установок - 23 закрытых сделки (2.7% install-to-deal conversion)
  • Средний CAC через App Store: $782 vs $1 240 через Google Ads
  • Keyword «field service management app» - лучший CAC ($490), увеличили ставки на 40%
  • Keyword «work order app» - 0 закрытых сделок за 4 месяца, отключили

Для кого актуально

B2B SaaS-компании, которые:

  • Имеют iOS-приложение как основной или значимый канал онбординга
  • Тратят на Apple Search Ads от $5 000/месяц
  • Ведут B2B pipeline в HubSpot, Salesforce или Kommo
  • Хотят видеть стоимость сделки, а не только стоимость установки

Актуально прежде всего для mobile-first B2B: field service, project management, logistics, sales enablement.

Аналогичный подход описан для других ad-каналов: Prooflytics + Google Ads, Prooflytics + Meta Ads. Apple Search Ads отличается механизмом атрибуции (device-level token вместо UTM/pixel), но логика замыкания на сделку идентична.

Часто задаваемые вопросы

Работает ли атрибуция без согласия ATT?

Да. AdServices Attribution API предоставляет два уровня данных: detailed (с согласием ATT) и limited (без согласия). В limited режиме доступны campaignId и adGroupId, но не keywordId и countryOrRegion. Для attribution на уровне кампании этого достаточно. Keyword-level доступен только с ATT consent.

Какой TTL у attributionToken?

24 часа с момента создания. Если не обменять токен за это время - он инвалидируется и данные теряются. Именно поэтому важно вызывать AAAttribution.attributionToken() и отправлять токен в Prooflytics немедленно при первом запуске, а не откладывать.

Нужно ли использовать MMP (AppsFlyer, Adjust) вместе с Prooflytics?

Одновременное использование возможно и дополняет картину. MMP закрывает Android-атрибуцию, cross-device, retargeting. Prooflytics использует AdServices для iOS-атрибуции и фокусируется на замыкании воронки на CRM-сделку - что MMP не делают. Для B2B SaaS с iOS-приложением достаточно Prooflytics без отдельного MMP.

Поддерживает ли Apple Search Ads attribution для подписок в App Store?

Да. conversionType в attribution payload может быть download или redownload. Для подписок через RevenueCat или StoreKit - атрибуция через AdServices работает аналогично: токен генерируется при установке, а событие подписки привязывается к attribution record через user_id.

Итог

Prooflytics + Apple Search Ads - атрибуция полного цикла для B2B мобильных приложений:

  • iOS app: AAAttribution.attributionToken() -> POST в Prooflytics
  • Backend: обмен токена на данные через Apple Attribution API
  • Привязка attribution record к CRM-лиду / сделке по user_id или email
  • При закрытии сделки: CAC = campaign spend / closed deals из App Store
  • Дашборд: лучшие keywords по CAC, воронка install -> closed deal

Если ваша B2B SaaS имеет iOS-приложение и вы хотите видеть реальный CAC из Apple Search Ads - свяжитесь с командой Prooflytics для настройки интеграции.

Ещё статьи

Все →