Adfacten Developers

Mobil SDK: uygulamanızda Adfacten reklamı gösterin

Adfacten mobil SDK'sı Android ve iOS uygulamalarında reklam gösterir. Reklamı ister, ekrana çizer, gösterim ve tıklamayı ölçer, ödüllü reklamda ödülü sunucunuza bildirtir. Bu sayfa yayıncılar (uygulama geliştiricileri) içindir. Uygulama kurulumlarını ölçmek isteyen reklamverenler MMP entegrasyonu sayfasına bakmalıdır.

English version: Mobile SDK.

Kaynak kod ve örnekler: Android (github.com/adfacten/adfacten-android-sdk), iOS (github.com/adfacten/adfacten-ios-sdk), Flutter (github.com/adfacten/adfacten_ads_flutter), React Native (github.com/adfacten/adfacten-react-native-ads) ve Unity (github.com/adfacten/adfacten-unity-ads). Panelde her yerleşim için hazır kod örneği de gösterilir.

Bu sayfada:

  1. Genel bakış: biçimler ve ücretlendirme
  2. Panelde başlangıç: uygulama, app-ads.txt, onay, yayına alma
  3. Android kurulumu
  4. iOS kurulumu
  5. Flutter, React Native ve Unity
  6. Ödüllü reklamda sunucu tarafı doğrulama
  7. Gizlilik ve onay (TCF, GPP, COPPA, KVKK)
  8. Doğrudan REST kullanımı (özel entegrasyon / mediation)
  9. Sorun giderme

1. Genel bakış

Biçim Ne zaman kullanılır
Banner Ekranın bir köşesinde sürekli duran reklam. Boyutlar: 320x50, 320x100, 300x250, 728x90 (yalnız tablet). Görünür olduğu sürece 30–120 saniyede bir yenilenir (yerleşim ayarı; 0 = yenileme yok), ekrandan çıkınca durur.
Interstitial (geçiş) Tam ekran; seviye sonu, ekran geçişi gibi doğal aralarda.
Rewarded (ödüllü) Kullanıcı reklamı sonuna kadar izler, karşılığında ödül alır (ör. 10 jeton). Ödül türü ve miktarı yerleşimde tanımlanır.
Native Reklam parçaları (başlık, metin, görsel, ikon, buton) sizin tasarımınıza yerleşir.
App open (açılış) Uygulama açılırken veya arka plandan dönerken gösterilen tam ekran reklam.

Ücretlendirme nasıl sayılır

SDK her olayı bir kez gönderir. İnternet yoksa olayları sıraya alır ve 24 saate kadar tekrar dener.

2. Panelde başlangıç

2.1 Uygulamayı ekleyin

Panel → Uygulamalar → Uygulama ekle.

Sonra uygulamaya yerleşim ekleyin (her reklam alanı için bir tane). Her yerleşimin bir kimliği vardır (plc_…) ve tek bir biçimi olur. Banner yerleşiminde boyut(lar)ı ve yenileme süresini, ödüllü yerleşimde ödül türünü ve miktarını seçersiniz. İsterseniz cihaz başına günlük gösterim sınırı da koyabilirsiniz.

2.2 Çocuklara yönelik uygulamalar desteklenmez

Adfacten reklamları çocuklara yönelik uygulamalarda gösterilmez (ABD COPPA ve Google Play Aileler politikası). Uygulama çocuklara yönelik işaretlenirse eklenmez; sonradan işaretlenirse tüm reklam istekleri reddedilir (coppa). Bu işareti yalnızca ağ ekibi kaldırabilir.

2.3 Test modu ve test reklamları

Yeni uygulamalar test modunda başlar. Test reklamları gerçek reklamlar gibi davranır (gösterim, görünürlük ve tıklama olayları çalışır, tıklama https://adfacten.com/ adresine gider) ama ücretlendirilmez ve üzerinde "Test reklamı" yazar.

Sunucu şu durumların herhangi birinde test reklamı döner:

Durum Nasıl düzelir
Uygulama onay bekliyor Ağ ekibinin onayı (2.5)
app-ads.txt satırı bulunamadı Satırı ekleyin (2.4)
Uygulamanın test modu açık Panelde test modunu kapatın (2.6)
SDK'da testMode açık (istekte test=true) Yayın sürümünde testMode kapalı olmalı
Cihaz emülatör / simülatör Gerçek cihazda deneyin

Test reklamlarının içeriği sabittir: banner istenen boyutta görsel, interstitial HTML, ödüllü reklam 5 saniyelik video (ödüllü), native hazır parçalar, açılış reklamı 320x480 görsel.

2.4 app-ads.txt satırı

app-ads.txt, uygulamanızın gerçekten size ait olduğunu ve Adfacten'in envanterinizi satmaya yetkili olduğunu gösterir (IAB app-ads.txt standardı). Gerçek reklamlar bu satır doğrulanınca gösterilir.

Nereye: Uygulamanın mağaza sayfasında yazan geliştirici web sitesinin kök dizinine: https://<geliştirici alan adı>/app-ads.txt. Alan adının başındaki www., m. ve mobile. yok sayılır (https://www.firma.com → https://firma.com/app-ads.txt). Önce HTTPS, sonra HTTP denenir.

Satır (panelde uygulamanızın sayfasında hazır gösterilir, kopyalayın):

adfacten.com, <yayıncı kimliğiniz>, DIRECT

Doğrulama: Uygulamayı eklediğinizde dosya hemen kontrol edilir, sonra yaklaşık 6 saatte bir yeniden kontrol edilir. Panelde Tekrar kontrol et ile dakikada bir kez elle kontrol ettirebilirsiniz.

Durum Anlamı
found Satır bulundu. Gerçek reklam gösterilebilir (onay ve test modu kapalıysa).
wrong_id adfacten.com satırı var ama yayıncı kimliği sizin değil (başka yerden kopyalanmış).
no_line Dosya var ama adfacten.com satırı yok.
missing Sitede app-ads.txt yok (404 veya dosya yerine HTML sayfa dönüyor).
no_website Mağaza sayfasında geliştirici web sitesi yazmıyor. Mağaza konsolundan ekleyin.
unreachable Site yanıt vermedi. Daha sonra yeniden denenir; son iyi sonuç korunur.
unknown Henüz kontrol edilmedi (ör. uygulama mağazada bulunamadı).

Mağaza sayfasındaki geliştirici web sitesini değiştirdiyseniz, mağazanın değişikliği yayınlaması zaman alabilir. Sonra Tekrar kontrol et'e basın.

2.5 Ağ onayı

Uygulamanız Adfacten ağ ekibinin onay kuyruğuna düşer. Onay için uygulamanın mağazada yayında olması gerekir. Onaylanınca veya reddedilince bildirim alırsınız; ret gerekçesi uygulamanın sayfasında görünür. Reddedilen uygulamalara reklam gösterilmez (app_not_approved).

2.6 Yayına alma

Gerçek reklamlar ancak şu dördü birlikte sağlanınca gösterilir:

  1. Uygulama onaylı,
  2. app-ads.txt durumu found,
  3. panelde uygulamanın test modu kapalı,
  4. uygulamanızın yayın sürümünde SDK'nın testMode ayarı kapalı.

Panelde uygulamanın sayfasındaki Yayın durumu neyin eksik olduğunu gösterir (live, test veya blocked ve nedeni).

2.7 sellers.json

Adfacten'in yetkili satıcı listesi: https://adfacten.com/sellers.json (IAB sellers.json). En az bir onaylı uygulaması olan yayıncılar listelenir. Şirketler adı ve alan adıyla görünür; şahıslar KVKK gereği gizli (is_confidential) listelenir, adınız yayımlanmaz.

3. Android

Gereksinim: Android 5.0 (API 21) ve üstü. Paket: com.adfacten.ads. Kaynak kod ve örnek uygulama: https://github.com/adfacten/adfacten-android-sdk (Apache 2.0).

3.1 Kurulum

settings.gradle.kts:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven("https://cdn.adfacten.com/maven/") {
            content { includeGroup("com.adfacten") }
        }
    }
}

Uygulama modülünün build.gradle.kts dosyası:

dependencies {
    implementation("com.adfacten:ads:1.0.1")

    // İsteğe bağlı: reklam kimliği (GAID) ve App Set ID
    implementation("com.adfacten:ads-gaid:1.0.1")
    // İsteğe bağlı: video reklamlar (Media3 ExoPlayer). Ödüllü reklam kullanıyorsanız önerilir.
    implementation("com.adfacten:ads-video:1.0.1")
}

3.2 Başlatma

Application.onCreate içinde bir kez çağırın. Ana iş parçacığını yormaz.

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        Adfacten.initialize(
            this,
            "apk_XXXXXXXXXXXXXXXXXXXXXXXX",
            AdfactenConfig.Builder()
                .setTestMode(BuildConfig.DEBUG)   // yayın sürümünde false
                .setLogLevel(LogLevel.WARN)
                .setChildDirected(false)
                .build()
        )
    }
}

Kullanıcı giriş yapınca (ödüllü reklam için) kullanıcı kimliğinizi verin:

Adfacten.setUserId("kullanici-123")   // sunucu ödül bildiriminde user_id olarak gelir

3.3 Onay (consent)

SDK, uygulamanızdaki IAB uyumlu onay aracının (CMP) yazdığı değerleri kendisi okur (IABTCF_gdprApplies, IABTCF_TCString, IABTCF_PurposeConsents, IABGPP_HDR_GppString, IABUSPrivacy_String). Ayrıca bir şey yapmanız gerekmez; onay aracını reklam yüklemeden önce çalıştırın. Ayrıntılar: bölüm 6.

3.4 Banner

XML ile:

<com.adfacten.ads.AdfactenBannerView
    android:id="@+id/banner"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:adf_placementId="plc_XXXXXXXX"
    app:adf_adSize="banner" />

adf_adSize: banner (320x50), large_banner (320x100), medium_rectangle (300x250), leaderboard (728x90, tablet).

Kod ile:

val banner = AdfactenBannerView(context).apply {
    placementId = "plc_XXXXXXXX"
    adSize = AdSize.BANNER
    listener = object : BannerAdListener {
        override fun onLoaded() {}
        override fun onFailed(error: AdError) { Log.w("ads", error.toString()) }
    }
}
container.addView(banner)
banner.load()

// Activity/Fragment yaşam döngüsü:
override fun onPause()   { banner.pause();   super.onPause() }
override fun onResume()  { super.onResume(); banner.resume() }
override fun onDestroy() { banner.destroy(); super.onDestroy() }

Banner görünür olduğu sürece yerleşimdeki süreye göre kendini yeniler.

3.5 Interstitial

private var interstitial: InterstitialAd? = null

fun loadInterstitial() {
    InterstitialAd.load(context, "plc_XXXXXXXX", object : AdLoadCallback<InterstitialAd> {
        override fun onLoaded(ad: InterstitialAd) { interstitial = ad }
        override fun onFailed(error: AdError) { interstitial = null }
    })
}

fun showInterstitial(activity: Activity) {
    val ad = interstitial ?: return
    if (ad.isExpired) { loadInterstitial(); return }
    ad.listener = object : FullScreenAdListener {
        override fun onClosed() { loadInterstitial() }   // bir sonrakini hazırlayın
    }
    ad.show(activity)
    interstitial = null   // bir reklam yalnızca bir kez gösterilir
}

3.6 Rewarded (ödüllü)

RewardedAd.load(context, "plc_XXXXXXXX", object : AdLoadCallback<RewardedAd> {
    override fun onLoaded(ad: RewardedAd) { rewarded = ad }
    override fun onFailed(error: AdError) { rewarded = null }
}, customData = "level=12")   // isteğe bağlı, ≤ 256 karakter, sunucu bildiriminde 'custom' olarak gelir

rewarded?.show(activity) { reward ->
    // Yalnızca arayüz için (ör. "Ödülünüz yolda" mesajı).
    // Ödülü SUNUCUNUZA gelen bildirime göre verin (bölüm 6).
}

3.7 Native

Kendi tasarımınızı NativeAdView içine koyun ve hangi görünümün hangi parçayı göstereceğini belirtin:

<com.adfacten.ads.NativeAdView
    android:id="@+id/native_ad"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:adf_titleView="@id/ad_title"
    app:adf_bodyView="@id/ad_body"
    app:adf_iconView="@id/ad_icon"
    app:adf_imageView="@id/ad_image"
    app:adf_callToActionView="@id/ad_cta"
    app:adf_adLabelView="@id/ad_label">
    <!-- kendi TextView / ImageView / Button görünümleriniz -->
</com.adfacten.ads.NativeAdView>
NativeAd.load(context, "plc_XXXXXXXX", object : AdLoadCallback<NativeAd> {
    override fun onLoaded(ad: NativeAd) { nativeAdView.setNativeAd(ad) }
    override fun onFailed(error: AdError) { nativeAdView.visibility = View.GONE }
})

NativeAdView gösterimi, görünürlüğü ve tıklamayı kendisi ölçer. "Reklam" etiketini her zaman görünür bırakın. Görünüm kaldırılınca ad.destroy() çağırın.

3.8 App open (açılış)

AppOpenAd.load(context, "plc_XXXXXXXX", object : AdLoadCallback<AppOpenAd> {
    override fun onLoaded(ad: AppOpenAd) { appOpen = ad }
    override fun onFailed(error: AdError) {}
})

// Uygulama ön plana gelince:
appOpen?.takeIf { !it.isExpired }?.show(activity)

Açılış reklamını ilk açılışta açılış ekranı (splash) bittikten sonra veya uygulama arka plandan döndüğünde gösterin; kullanıcının işini bölmeyin.

3.9 Google Play "Veri güvenliği" formu

SDK'nın topladığı ve Adfacten'e gönderdiği veriler (formu doldururken kendi uygulamanızın verileriyle birleştirin):

Veri Ne zaman Amaç
Cihaz veya diğer kimlikler: SDK kurulum kimliği Her zaman Reklam, dolandırıcılık önleme
Cihaz veya diğer kimlikler: reklam kimliği (GAID), App Set ID Yalnız ads-gaid eklenirse ve izin varsa Reklam, analiz
Uygulama etkileşimleri (reklam gösterimi, tıklama) Her zaman Reklam, analiz
Kullanıcı kimliği (userId) Yalnız siz verirseniz (ödüllü reklam) Uygulama işlevi (ödül)
IP adresinden türetilen yaklaşık konum (ülke) Sunucuda Reklam hedefleme, dolandırıcılık önleme

Veriler aktarım sırasında şifrelenir (HTTPS) ve üçüncü tarafla (Adfacten) paylaşılır.

4. iOS

Gereksinim: iOS 15 ve üstü. Modül: AdfactenAds. Üçüncü taraf bağımlılığı yoktur; video için AVPlayer kullanılır.

4.1 Kurulum

Kaynak kod: https://github.com/adfacten/adfacten-ios-sdk (Apache 2.0). Üç yoldan birini seçin:

Swift Package Manager: Xcode → File → Add Package Dependencies… → https://github.com/adfacten/adfacten-ios-sdk adresini girin ve AdfactenAds ürününü ekleyin.

// Package.swift kullanan projeler:
.package(url: "https://github.com/adfacten/adfacten-ios-sdk.git", from: "1.0.0")

CocoaPods:

pod 'AdfactenAds', :git => 'https://github.com/adfacten/adfacten-ios-sdk.git', :tag => '1.0.0'

Elle (XCFramework): https://cdn.adfacten.com/ios/AdfactenAds-1.0.0.xcframework.zip dosyasını indirin, açın ve Xcode'da hedefinize Embed & Sign olarak ekleyin. SHA-256: 6ab9883dc8d92597f2f0c7391c4b2434c15950548e7e465e16367539290b883f

Hemen denemek için depodaki örnek uygulamayı çalıştırın: Adfacten'in herkese açık test uygulamasının anahtarlarıyla gelir ve ayar gerektirmeden (ücretlendirilmeyen) test reklamları gösterir.

4.2 Başlatma

Uygulama açılışında, ana iş parçacığında bir kez çağırın:

import AdfactenAds

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let config = AdfactenConfig()
    #if DEBUG
    config.testMode = true            // yayın sürümünde false
    #endif
    config.childDirected = false
    Adfacten.initialize(appKey: "apk_XXXXXXXXXXXXXXXXXXXXXXXX", config: config)
    return true
}

// Kullanıcı giriş yapınca (ödüllü reklam için):
Adfacten.setUserId("kullanici-123")

4.3 App Tracking Transparency (ATT) ve gizlilik bildirimi

4.4 Banner

let banner = AdfactenBannerView(placementId: "plc_XXXXXXXX", size: .banner)
banner.rootViewController = self
banner.delegate = self   // AdfactenBannerViewDelegate (isteğe bağlı)
view.addSubview(banner)  // boyutu kendiliğinden bilir (intrinsicContentSize)
banner.load()

Boyutlar: .banner (320x50), .largeBanner (320x100), .mediumRectangle (300x250), .leaderboard (728x90, tablet). SwiftUI için:

AdfactenBanner(placementId: "plc_XXXXXXXX", size: .banner)

4.5 Interstitial

var interstitial: InterstitialAd?

func loadInterstitial() {
    InterstitialAd.load(placementId: "plc_XXXXXXXX") { [weak self] ad, error in
        self?.interstitial = ad
    }
}

func showInterstitial() {
    guard let ad = interstitial, ad.isReady else { loadInterstitial(); return }
    ad.onDismissed = { [weak self] in self?.loadInterstitial() }
    ad.present(from: self)
    interstitial = nil
}

async sürümü de vardır: let ad = try await InterstitialAd.load(placementId: "plc_…").

4.6 Rewarded (ödüllü)

RewardedAd.load(placementId: "plc_XXXXXXXX", customData: "level=12") { [weak self] ad, error in
    self?.rewarded = ad
}

rewarded?.present(from: self) { reward in
    // Yalnızca arayüz için. Ödülü sunucunuza gelen bildirime göre verin (bölüm 6).
}

4.7 Native

NativeAdView alt sınıfınızda (kodda veya Interface Builder'da) şu bağlantıları kurun: titleLabel, bodyLabel, brandLabel, callToActionButton, mediaView, iconView, adLabel.

NativeAd.load(placementId: "plc_XXXXXXXX") { [weak self] ad, error in
    self?.nativeAdView.nativeAd = ad   // bağlar, gösterimi ve tıklamayı ölçer
}

Hücre yeniden kullanılırken nativeAdView.nativeAd = nil yapın. adLabel bağlamazsanız SDK kendi "Reklam" etiketini ekler.

4.8 App open (açılış)

AppOpenAd.load(placementId: "plc_XXXXXXXX") { [weak self] ad, error in
    self?.appOpen = ad
}

// Uygulama ön plana gelince:
if let ad = appOpen, ad.isReady { ad.present() }

5. Flutter, React Native ve Unity

Android ve iOS kütüphanelerinin üstüne yazılmış resmi paketler; reklam türleri, test modu ve ödül bildirimi yukarıdaki bölümlerle aynıdır.

5.1 Flutter

Kaynak kod ve örnek uygulama: https://github.com/adfacten/adfacten_ads_flutter (Apache 2.0). pubspec.yaml:

dependencies:
  adfacten_ads:
    git:
      url: https://github.com/adfacten/adfacten_ads_flutter
      ref: 1.0.1
await Adfacten.initialize('apk_XXXXXXXXXXXXXXXXXXXXXXXX');

AdfactenBanner(placementId: 'plc_XXXXXXXX', size: AdSize.banner);

final ad = await RewardedAd.load('plc_XXXXXXXX');
await ad.show(onUserEarnedReward: (reward) => grant(reward));

Native reklamlar kütüphanenin hazır şablonlarıyla (küçük satır, orta kart) gösterilir; gösterim ve tıklama yalnız böyle doğru sayılır. Ayrıntılar paketin README dosyasında.

5.2 React Native ve Expo

Kaynak kod ve örnek uygulama: https://github.com/adfacten/adfacten-react-native-ads (Apache 2.0). React Native 0.80 ve üstü (New Architecture).

npm install github:adfacten/adfacten-react-native-ads#1.0.1
cd ios && pod install
import { Adfacten, AdfactenBanner, RewardedAd } from '@adfacten/react-native-ads';

Adfacten.initialize('apk_XXXXXXXXXXXXXXXXXXXXXXXX');
<AdfactenBanner placementId="plc_XXXXXXXX" size="banner" />;

Expo: app.json içinde "plugins": ["@adfacten/react-native-ads"] ekleyip npx expo prebuild veya geliştirme derlemesi (dev client) kullanın. Expo Go yerel modül yükleyemediği için desteklenmez.

Her iki paketin örnek uygulaması Adfacten'in herkese açık test uygulamasının anahtarlarıyla gelir ve ayar gerektirmeden (ücretlendirilmeyen) test reklamları gösterir.

5.3 Unity

Kaynak kod ve örnek sahne: https://github.com/adfacten/adfacten-unity-ads (Apache 2.0). Unity 2021.3 LTS ve üstü. Window › Package Manager › + › Add package from git URL ile şu adresi ekleyin:

https://github.com/adfacten/adfacten-unity-ads.git#1.0.0

Android ve iOS bağımlılıkları External Dependency Manager (EDM4U) ile kurulur; uygulama anahtarlarını Project Settings › Adfacten ekranına yazın.

Adfacten.Initialize("apk_XXXXXXXXXXXXXXXXXXXXXXXX");

var banner = new BannerView("plc_XXXXXXXX", AdSize.Banner, AdPosition.Bottom);
banner.Load();

RewardedAd.Load("plc_XXXXXXXX", (ad, error) => {
    if (ad != null) ad.Show(reward => { /* yalnız arayüz; ödülü sunucu bildirimiyle verin */ });
});

Editörde gerçek reklam yerine yer tutucu reklamlar görünür; böylece oyunu telefon olmadan geliştirebilirsiniz.

6. Ödüllü reklamda sunucu tarafı doğrulama

Ödülü yalnızca sunucunuza gelen Adfacten bildirimine göre verin. Uygulamadaki onUserEarnedReward geri çağrısı yalnızca arayüz içindir; değiştirilmiş bir uygulama onu sahte çağırabilir.

6.1 Kurulum

Panel → Uygulamalar → uygulamanız → Ödül bildirimi:

6.2 Bildirim

Kullanıcı ödüllü reklamı geçerli biçimde tamamlayınca Adfacten sunucunuza bir GET isteği gönderir:

GET https://api.oyununuz.com/adfacten/reward?env=prod&user_id=kullanici-123&custom=level%3D12&reward_type=coins&reward_amount=10&transaction_id=imp_…&placement=plc_XXXXXXXX&ts=1794160000&sig=5f1c…e9
Parametre Değer
(sizin parametreleriniz) Bildirim adresinde yazdıklarınız, en başta
user_id Adfacten.setUserId / userId ile verdiğiniz kimlik (en çok 128 karakter; yoksa boş)
custom Reklamı yüklerken verdiğiniz customData (en çok 256 karakter; yoksa boş)
reward_type Yerleşimdeki ödül türü, ör. coins
reward_amount Yerleşimdeki ödül miktarı (tam sayı)
transaction_id Bu gösterimin tekil kimliği. Her ödül için bir tane.
placement Yerleşim kimliği (plc_…)
ts Ödülün oluştuğu an (Unix saniye). Tekrar denemelerde değişmez.
test Yalnızca test reklamında: 1
sig İmza, her zaman en sonda

Parametreler bu sırayla gelir ve değerler URL kodlamalıdır (ör. boşluk %20).

6.3 İmza nasıl doğrulanır

sig = hex( HMAC-SHA256( gizli_anahtar, ham_sorgu ) )

ham_sorgu, isteğin olduğu gibi gelen sorgu metnidir: ? işaretinden sonraki her şey, sondaki &sig=… kısmı hariç. Kendi parametreleriniz (varsa) ve test=1 (varsa) imzaya dahildir.

Node.js (Express)

import crypto from "node:crypto";

const SECRETS = [process.env.ADFACTEN_REWARD_SECRET, process.env.ADFACTEN_REWARD_SECRET_OLD].filter(Boolean);

function verifyAdfacten(rawQuery) {
  const i = rawQuery.lastIndexOf("&sig=");
  if (i < 0) return false;
  const signed = rawQuery.slice(0, i);
  const sig = rawQuery.slice(i + 5);
  if (!/^[0-9a-f]{64}$/.test(sig)) return false;
  const given = Buffer.from(sig, "hex");
  return SECRETS.some((s) =>
    crypto.timingSafeEqual(crypto.createHmac("sha256", s).update(signed, "utf8").digest(), given));
}

app.get("/adfacten/reward", async (req, res) => {
  const url = req.originalUrl;
  const raw = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
  if (!verifyAdfacten(raw)) return res.sendStatus(403);
  const q = new URLSearchParams(raw);
  await grantOnce(q.get("transaction_id"), q.get("user_id"), q.get("reward_type"),
                  Number(q.get("reward_amount")), q.get("test") === "1");
  res.sendStatus(200);
});

Python (FastAPI / Starlette)

import hashlib, hmac, os, re
from urllib.parse import parse_qs
from fastapi import FastAPI, Request, Response

SECRETS = [s for s in (os.environ.get("ADFACTEN_REWARD_SECRET"), os.environ.get("ADFACTEN_REWARD_SECRET_OLD")) if s]

def verify_adfacten(raw_query: str) -> bool:
    i = raw_query.rfind("&sig=")
    if i < 0:
        return False
    signed, sig = raw_query[:i], raw_query[i + 5:]
    if not re.fullmatch(r"[0-9a-f]{64}", sig):
        return False
    return any(
        hmac.compare_digest(hmac.new(s.encode(), signed.encode(), hashlib.sha256).hexdigest(), sig)
        for s in SECRETS
    )

app = FastAPI()

@app.get("/adfacten/reward")
async def reward(request: Request) -> Response:
    raw = request.scope["query_string"].decode("latin-1")   # Flask: request.query_string.decode()
    if not verify_adfacten(raw):
        return Response(status_code=403)
    q = {k: v[0] for k, v in parse_qs(raw, keep_blank_values=True).items()}
    await grant_once(q["transaction_id"], q["user_id"], q["reward_type"], int(q["reward_amount"]), q.get("test") == "1")
    return Response(status_code=200)

PHP

<?php
function verify_adfacten(string $raw, array $secrets): bool {
    $i = strrpos($raw, '&sig=');
    if ($i === false) return false;
    $signed = substr($raw, 0, $i);
    $sig = substr($raw, $i + 5);
    if (!preg_match('/^[0-9a-f]{64}$/', $sig)) return false;
    foreach ($secrets as $s) {
        if (hash_equals(hash_hmac('sha256', $signed, $s), $sig)) return true;
    }
    return false;
}

$uri = $_SERVER['REQUEST_URI'];                       // ham adres (QUERY_STRING yeniden yazılmış olabilir)
$raw = ($p = strpos($uri, '?')) === false ? '' : substr($uri, $p + 1);
$secrets = array_filter([getenv('ADFACTEN_REWARD_SECRET'), getenv('ADFACTEN_REWARD_SECRET_OLD')]);

if (!verify_adfacten($raw, $secrets)) { http_response_code(403); exit; }
parse_str($raw, $q);
grant_once($q['transaction_id'], $q['user_id'], $q['reward_type'], (int)$q['reward_amount'], ($q['test'] ?? '') === '1');
http_response_code(200);

Go

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"net/http"
	"net/url"
	"os"
	"strings"
)

var secrets = nonEmpty(os.Getenv("ADFACTEN_REWARD_SECRET"), os.Getenv("ADFACTEN_REWARD_SECRET_OLD"))

func verifyAdfacten(raw string) bool {
	i := strings.LastIndex(raw, "&sig=")
	if i < 0 {
		return false
	}
	signed, given := raw[:i], raw[i+5:]
	want, err := hex.DecodeString(given)
	if err != nil || len(want) != sha256.Size {
		return false
	}
	for _, s := range secrets {
		m := hmac.New(sha256.New, []byte(s))
		m.Write([]byte(signed))
		if hmac.Equal(m.Sum(nil), want) {
			return true
		}
	}
	return false
}

func rewardHandler(w http.ResponseWriter, r *http.Request) {
	raw := r.URL.RawQuery
	if !verifyAdfacten(raw) {
		w.WriteHeader(http.StatusForbidden)
		return
	}
	q, _ := url.ParseQuery(raw)
	if err := grantOnce(r.Context(), q.Get("transaction_id"), q.Get("user_id"), q.Get("reward_type"),
		q.Get("reward_amount"), q.Get("test") == "1"); err != nil {
		w.WriteHeader(http.StatusInternalServerError) // geçici hata: Adfacten tekrar dener
		return
	}
	w.WriteHeader(http.StatusOK)
}

6.4 Aynı ödülü iki kez vermeyin

6.5 Yanıt, tekrar deneme, kayıt

6.6 Test reklamları

Test reklamının ödül bildiriminde test=1 gelir. Bu bildirimler entegrasyonu denemek içindir: gerçek değer vermeyin (ya da yalnızca test ortamında verin).

6.7 Gizli anahtarı yenileme

Panel → Ödül bildirimi → Yeni gizli anahtar. Yeni anahtar hemen geçerli olur ve bir kez gösterilir; bekleyen tekrar denemeler de yeni anahtarla imzalanır. Kesintisiz geçiş için:

  1. Yeni anahtarı alın ve sunucunuza eskisiyle birlikte ekleyin (yukarıdaki örneklerde …_OLD).
  2. Birkaç saat sonra eski anahtarı silin.

Geçiş sırasında imzası tutmayan bildirimler kaybolmaz, tekrar denenir.

7. Gizlilik ve onay

Durum SDK / sunucu ne yapar
GDPR / TCF: CMP IABTCF_gdprApplies=1 yazmış Reklam kimliği (GAID/IDFA) kullanılmaz; sıklık ve eşleştirme SDK kurulum kimliğiyle yapılır; tıklama adreslerinde cihaz kimliği makroları doldurulmaz. 1. amaç (cihazda bilgi saklama) onayı yoksa SDK reklam kimliğini ve IDFV'yi hiç göndermez.
GPP: IABGPP_HDR_GppString dolu Reklam kimliği kullanılmaz, cihaz kimliği makroları doldurulmaz.
ABD gizliliği: IABUSPrivacy_String 3. karakteri Y (satış itirazı) Aynı şekilde: reklam kimliği kullanılmaz.
Reklam takibi sınırlı (LMT): Android'de reklam kimliği silinmiş / sınırlı, iOS'ta ATT izni yok Reklam kimliği kullanılmaz; reklamlar gösterilmeye devam eder.
COPPA: childDirected = true veya uygulama panelde çocuklara yönelik Reklam gösterilmez; sunucu 403 coppa döner.

8. Doğrudan REST kullanımı

SDK'yı kullanamıyorsanız (ör. kendi mediation katmanınız, oyun motoru eklentisi) aynı uçları doğrudan çağırabilirsiniz. Bu bir herkese açık, geriye dönük uyumlu arayüzdür: alanlar yalnızca eklenir, bilinmeyen alanları yok sayın.

8.1 Reklam isteği

POST https://trk.adfacten.com/m/v1/ad
Content-Type: application/json
User-Agent: AdfactenSDK/1.0.0 (android 15; SM-A546B) com.firma.uygulama/1.2.3
X-Adfacten-SDK: android/1.0.0
{
  "v": 1,
  "app_key": "apk_XXXXXXXXXXXXXXXXXXXXXXXX",
  "placement": "plc_XXXXXXXX",
  "format": "banner",
  "sizes": ["320x50"],
  "device": {
    "os": "android", "osv": "15", "make": "Samsung", "model": "SM-A546B",
    "sdk_id": "3f6c0a52-8f7e-4d1b-9a0c-1b2c3d4e5f60",
    "ifa": "", "ifa_type": "sdk", "lmt": false,
    "lang": "tr", "w": 1080, "h": 2340, "pxratio": 2.75,
    "connection": "wifi", "carrier": "", "tz_offset_min": 180
  },
  "app": { "bundle": "com.firma.uygulama", "version": "1.2.3", "sdk_version": "1.0.0" },
  "regs": { "coppa": false, "gdpr": 0, "tcf": "", "gpp": "", "us_privacy": "" },
  "user": { "id": "kullanici-123", "custom": "level=12" },
  "test": false
}

8.2 Yanıt

{
  "ad": {
    "id": "opak, imzalı gösterim kimliği",
    "format": "banner",
    "kind": "image",
    "test": false,
    "w": 320, "h": 50,
    "image": { "url": "https://cdn.adfacten.com/….webp", "url_2x": "…", "fallback": "….jpg" },
    "click_url": "https://trk.adfacten.com/a/click?imp=…",
    "trackers": {
      "render":   "https://trk.adfacten.com/i?imp=…&e=render",
      "view":     "https://trk.adfacten.com/i?imp=…&e=view",
      "complete": "https://trk.adfacten.com/i?imp=…&e=complete",
      "close":    "https://trk.adfacten.com/i?imp=…&e=close"
    },
    "view_rule": { "min_ratio": 0.5, "min_ms": 1000 },
    "close_after_s": 0,
    "reward": { "type": "coins", "amount": 10 },
    "label": "Reklam",
    "expires_at": 1794160000
  }
}

kind değerine göre içerik:

kind Alan Nasıl çizilir
image image Görsel; tıklanınca click_url
html html Korumalı (sandbox) WebView, JavaScript köprüsü yok. WebView'in kendisi gezinmemeli: her gezinmeyi ve window.open'ı yakalayıp dışarıda (tarayıcıda) açın.
video vast VAST 4.1 (interstitial / ödüllü).
native native title, body, brand, cta, image, icon parçaları

8.3 Olay bildirimleri (beacon)

Her tracker adresine sonuna &sid=<device.sdk_id>&ts=<milisaniye> ekleyip GET (veya boş gövdeli POST) gönderin. sid, reklamı isteyen sdk_id ile aynı olmalıdır; başka bir sid ile gelen olaylar ücretlendirilmez.

Olay Ne zaman
render Reklam ekrana gerçekten çizildiğinde. Ücretlendirilen gösterim budur.
view view_rule sağlanınca (en az %50 görünür, en az min_ms; video için 2 saniye)
complete Video sonuna kadar izlenince. Video olmayan ödüllü reklamda: reklam ekrandayken close_after_s geçince. Ödül buna bağlıdır; erken gelen complete yok sayılır.
close Kullanıcı kapatınca (kaydedilir, ücretlendirilmez)

8.4 Hata kodları

Hatalı isteklerde gövde {"code": "...", "message": "..."} olur. Bu hataları tekrar denemeyin; yalnızca 5xx ve ağ hatalarında geri çekilerek tekrar deneyin.

HTTP code Anlamı
400 invalid_request Gövde okunamadı veya zorunlu alan eksik
400 placement_mismatch Yerleşim bulunamadı, başka bir uygulamanın, ya da format yerleşimin biçimiyle aynı değil
403 unknown_app Uygulama anahtarı yok
403 app_not_approved Uygulama reddedildi
403 platform_mismatch Anahtar diğer platformun (ör. iOS anahtarı Android'de)
403 bundle_mismatch app.bundle kayıtlı paket adıyla aynı değil
403 coppa Uygulama çocuklara yönelik veya regs.coppa açık
403 test_only İleride kullanım için ayrıldı (v1'de gönderilmez)

9. Sorun giderme

SDK'da hata nesnesinin serverCode alanı yukarıdaki sunucu kodunu taşır. Ayrıntılı kayıt için logLevel'ı geçici olarak Android'de LogLevel.DEBUG, iOS'ta .debug yapın (Android Logcat etiketi: Adfacten).

Belirti Neden Çözüm
unknown_app Uygulama anahtarı yanlış yazılmış veya başka ortamın anahtarı Panelden apk_… anahtarını kopyalayın
bundle_mismatch Uygulamanın paket adı (ör. .debug son ekli derleme) kayıtlı paket adından farklı Kayıtlı paket adıyla derleyin; debug derlemesinde applicationIdSuffix kullanmayın
platform_mismatch Android anahtarı iOS'ta (ya da tersi) Her platform için ayrı uygulama ekleyin, kendi anahtarını kullanın
placement_mismatch Yerleşim kimliği yanlış, başka uygulamanın, arşivlenmiş ya da biçim tutmuyor (ör. banner yerleşimi RewardedAd ile yüklenmiş) Doğru plc_… kimliğini ve doğru reklam sınıfını kullanın
app_not_approved Uygulama reddedildi Ret gerekçesini panelde okuyun, düzeltip destek ekibine yazın
coppa childDirected açık veya uygulama çocuklara yönelik işaretli Çocuklara yönelik uygulamalarda Adfacten kullanılamaz
Hep test reklamı geliyor Bölüm 2.3'teki koşullardan biri Paneldeki Yayın durumu'na bakın: onay bekliyor → onayı bekleyin; app-ads.txt → satırı ekleyin, Tekrar kontrol et; test modu → kapatın; SDK'da testMode açık → kapatın; emülatör → gerçek cihaz
Reklam gelmiyor (204; Android NO_FILL, iOS .noFill) O an uygun reklam yok, yerleşim durdurulmuş, günlük sıklık sınırı dolmuş veya trafik bot/veri merkezi sayılmış Normaldir; SDK 30 saniyeden önce tekrar istemez. Yerleşimin aktif olduğunu kontrol edin, VPN kullanmadan gerçek cihazda deneyin
VIDEO_NOT_SUPPORTED (Android) Video reklam geldi ama ads-video eklenmemiş com.adfacten:ads-video bağımlılığını ekleyin
Android AD_EXPIRED, iOS .expired Reklam yüklendikten sonra yaklaşık 55 dakika içinde gösterilmedi Göstermeden kısa süre önce yükleyin; kapatınca yenisini yükleyin
Ödül bildirimi gelmiyor Bildirim adresi girilmemiş, adres https değil, sunucunuz 2xx dönmüyor ya da imza doğrulaması hatalı Paneldeki ödül bildirimleri kaydında durum kodunu ve hatayı okuyun