SKAdNetwork 3.0 S2S 実装ガイド (レガシー)

レガシー — SKAdNetwork 3。 このガイドはレガシーの SKAdNetwork 3.0 実装を対象としており、既存の連携のためだけに 残されています。新規およびすべての現行実装には、 SKAdNetwork 4 実装ガイド をご利用ください。

SKAdNetwork 3.0 S2S 実装ガイド (レガシー)

Apple の SKAdNetwork 3.0 フレームワークを実装し、server-to-server 連携を使用して、 ユーザー識別子を損なうことなくアプリインストールキャンペーンのパフォーマンスを測定する、 プライバシーに準拠した iOS アトリビューションを実現します。


概要

SKAdNetwork とは

SKAdNetwork は Apple のプライバシー保護型アトリビューションフレームワークであり、 ユーザーレベルの識別子を必要とせずに、iOS 上でアプリインストール広告キャンペーンの コンバージョン率を測定できるようにします。

このフレームワークは App Store サーバーを通じてアトリビューションを処理し、 アトリビューション情報を広告ネットワークに送信する前に、ユーザー識別子や時間データから 切り離します。

Apple のドキュメント: SKAdNetwork Framework Reference


SKAdNetwork の仕組み

アトリビューションの処理は完全に Apple のインフラを通じて行われ、キャンペーンの パフォーマンス測定を可能にしながらユーザーのプライバシーを確保します。

SKAdNetwork アトリビューションフロー

アトリビューションフロー:

  1. 広告クリック: ユーザーが、ネットワーク、パブリッシャー、キャンペーン ID を含む SKAdNetwork 署名付きの広告をクリックします
  2. App Store の起動: デバイスがアトリビューションデータを保存し、 インストールのために App Store を開きます
  3. アプリの起動: ユーザーがアプリをインストールし、初めて 起動します
  4. 登録: アプリが SKAdNetwork フレームワークに登録し、 インストールの成功を通知します
  5. conversion value: アプリは任意で conversion value (0~63) を更新し、インストール後のアクティビティを表現します
  6. タイマーウィンドウ: デバイスは初回起動または 最後の conversion value 更新から 24 時間以上待機します
  7. ポストバック: App Store が、キャンペーン ID、conversion value、 暗号署名を含むアトリビューションポストバックを広告ネットワークに送信します

プライバシー・バイ・デザイン:

  • ポストバックにはデバイスやユーザーの識別子が含まれません
  • 最短 24 時間の遅延により、時間的な相関を防ぎます
  • アプリはユーザーがどの広告をクリックしたかを知ることはありません
  • ネットワークはどの特定のユーザーがインストールしたかを知ることはありません

機能と制限

SKAdNetwork が提供するもの:

  • ラストクリックアトリビューション: ユーザーの同意や ATT オプトインなしで機能します
  • キャンペーンの内訳: ソース、キャンペーン、パブリッシャー 単位の粒度
  • conversion value: インストール後の測定のための 64 個の離散値 (0~63)
  • 不正防止: 暗号署名がアトリビューションの 真正性を検証します

現在の制限:

  • ユーザーレベルのデータなし: 個々のユーザーの 行動を追跡できません
  • ビュースルーなし: クリックベースのアトリビューションのみ
  • conversion value の制限: インストールごとに単一の 6 ビット値 (0~63)
  • 粒度の制限: 最大 100 個のキャンペーン ID、 広告グループやクリエイティブ単位の内訳はなし
  • 長期コホートなし: コンバージョン測定のための 期間が限られています
  • 不正リスク: conversion value 自体は署名されておらず、 ポストバックが重複する可能性があります

Singular のリソース

Singular は SKAdNetwork の実装と最適化のための包括的なリソースを 提供しています。


Singular SKAdNetwork ソリューション

Singular の SKAdNetwork ソリューションは、クライアントサイドの実装から ポストバック処理、キャンペーン最適化まで、エンドツーエンドのアトリビューション管理を提供します。

ソリューションの構成要素

プラットフォーム機能

アトリビューションと分析のワークフロー全体にわたる包括的な SKAdNetwork サポート。

構成要素 機能
クライアントサイドコード SKAdNetwork フレームワーク登録と conversion value 管理のための ネイティブ iOS コードサンプル
ポストバック処理 すべての広告ネットワークからのポストバックの検証と集計、および 統合されたレポート
不正対策 暗号署名の検証、トランザクション ID の重複排除、 および安全なパラメータの検証
コンバージョン管理 インストール後のアクティビティを conversion value にエンコードするための 動的なダッシュボード設定
レポート 粒度の高い分析のための、キャンペーン ID の変換とマーケティングパラメータによる エンリッチメント
パートナーポストバック パートナー最適化のために、デコードされた conversion value を イベントおよび収益として送信

S2S 連携アーキテクチャ

実装の構成要素

server-to-server の SKAdNetwork 連携は、2 つの主要な実装領域で 構成されます。

1. クライアントサイド実装 (必須):

  • アプリ起動時の SKAdNetwork フレームワーク登録
  • インストール後のアクティビティに基づくインテリジェントな conversion value 管理
  • SKAdNetwork アトリビューションを使用したキャンペーン最適化に不可欠

2. S2S 連携の更新 (任意):

  • Singular の S2S イベントとセッションを SKAdNetwork メタデータでエンリッチします
  • 実装の検証とトラブルシューティングを可能にします
  • デバッグ用に conversion value のタイムスタンプを提供します

クライアントサイド実装

最適なキャンペーン測定のために、Singular のネイティブ iOS コードサンプルを使用して、 SKAdNetwork フレームワーク登録と conversion value 管理を実装します。

実装の責任範囲

中核となる機能

クライアントサイドコードは、SKAdNetwork 測定のための 2 つの重要な機能を処理します。

  1. SKAdNetwork 登録: アトリビューションを有効にするため、起動直後に アプリをフレームワークに登録します
  2. conversion value 管理:
    • 次の conversion value を受け取るために、Singular エンドポイントと 同期的に通信します
    • セッション、イベント、収益を Singular に報告します
    • 設定されたインストール後アクティビティを表す、エンコードされた conversion value を受け取ります
    • SKAdNetwork メタデータ (初回呼び出しのタイムスタンプ、最後の 呼び出しのタイムスタンプ、現在の値) を収集します

SKAdNetwork インターフェース

メソッド定義

インターフェースには、フレームワーク登録、セッショントラッキング、 イベントトラッキング、収益測定のためのメソッドが含まれます。

SKANSnippet.h
//  SKANSnippet.h

#import <Foundation/Foundation.h>

@interface SKANSnippet : NSObject

// Register for SKAdNetwork attribution.
// Call this method as soon as possible once app is launched
+ (void)registerAppForAdNetworkAttribution;

// Track retention and cohorts by calling after each session.
// Reports session details and updates conversion value if needed.
// Conversion value updates only if new value greater than previous.
// Optional callback runs once conversion value updated.
+ (void)updateConversionValueAsync:(void(^)(int, NSError*))handler;

// Track conversion events by calling after each event.
// Reports event details and updates conversion value if needed.
// Optional callback runs once conversion value updated.
+ (void)updateConversionValueAsync:(NSString*)eventName
               withCompletionHandler:(void(^)(int, NSError*))handler;

// Track revenue by calling before each revenue event.
// Updates total revenue for next conversion value calculation.
// Note:
// 1. Call before 'updateConversionValueAsync' to ensure revenue included
// 2. Avoid calling on event retries to prevent double-counting
+ (void)updateRevenue:(double)amount andCurrency:(NSString*)currency;

// Gets current conversion value (nil if none set)
+ (NSNumber *)getConversionValue;

@end

登録メソッド

registerAppForAdNetworkAttribution はアプリを SKAdNetwork フレームワークに登録し、24 時間のアトリビューションタイマーを開始します。

タイマーの挙動:

  • デバイスはタイマーが切れてから 0~24 時間後にインストール通知を送信します
  • より高い値で conversion value を更新すると、タイマーがリセットされ 新たな 24 時間の間隔が始まります
  • Apple のリファレンス: updateConversionValue Documentation

conversion value メソッド

これらのメソッドは、インストール後のアクティビティと設定されたコンバージョンモデルに基づいて、 conversion value を計算および更新します。

対応するアクティビティ:

  • セッション: リテンション測定に不可欠
  • コンバージョンイベント: インストール後のイベント 測定に不可欠
  • 収益イベント: 収益測定に不可欠

インターフェースの実装

完全な実装コード

完全な実装では、登録、コンバージョン管理、および Singular API との 通信を処理します。

SKANSnippet.m
//  SKANSnippet.m

#import "SKANSnippet.h"
#import <StoreKit/SKAdNetwork.h>

#define SESSION_EVENT_NAME @"__SESSION__"
#define SINGULAR_API_URL @"https://sdk-api-v1.singular.net/api/v1/conversion_value"

// Keys for UserDefaults storage
#define CONVERSION_VALUE_KEY @"skan_conversion_value"
#define FIRST_SKAN_CALL_TIMESTAMP @"skan_first_call_to_skadnetwork_timestamp"
#define LAST_SKAN_CALL_TIMESTAMP @"skan_last_call_to_skadnetwork_timestamp"
#define TOTAL_REVENUE_BY_CURRENCY_KEY @"skan_total_revenue_by_currency"
#define SECONDS_PER_DAY 86400

static NSLock *lockObject;

@implementation SKANSnippet

+ (void)registerAppForAdNetworkAttribution {
    if ([SKANSnippet getFirstSkanCallTimestamp] != 0) {
        return;
    }

    if (@available(iOS 11.3, *)) {
        [SKAdNetwork registerAppForAdNetworkAttribution];
        [SKANSnippet setFirstSkanCallTimestamp];
        [SKANSnippet setLastSkanCallTimestamp];
    }
}

+ (void)updateConversionValueAsync:(void(^)(int, NSError*))handler {
    [SKANSnippet updateConversionValueAsync:SESSION_EVENT_NAME withCompletionHandler:handler];
}

+ (void)updateConversionValueAsync:(NSString*)eventName withCompletionHandler:(void(^)(int, NSError*))handler {
    if (@available(iOS 14, *)) {
        if ([SKANSnippet isSkanUpdateWindowOver]) {
            return;
        }

        [SKANSnippet getConversionValueFromServer:eventName withCompletionHandler:handler];
    }
}

+ (void)updateRevenue:(double)amount andCurrency:(NSString*)currency {
    if (@available(iOS 14, *)) {

        // Update total revenues
        if (amount != 0 && currency) {
            NSMutableDictionary* revenues = [[SKANSnippet getTotalRevenue] mutableCopy];
            NSNumber* currentRevenue = @(0);

            if ([revenues objectForKey:currency]) {
                currentRevenue = [revenues objectForKey:currency];
            }

            currentRevenue = @([currentRevenue floatValue] + amount);
            [revenues setObject:currentRevenue forKey:currency];

            [SKANSnippet setTotalRevenue:revenues];
        }
    }
}

+ (NSNumber *)getConversionValue {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    if (![userDefaults objectForKey:CONVERSION_VALUE_KEY]) {
        return nil;
    }
    return @([userDefaults integerForKey:CONVERSION_VALUE_KEY]);
}

+ (void)getConversionValueFromServer:(NSString*)eventName withCompletionHandler:(void(^)(int, NSError*))handler {
    if (!lockObject) {
        lockObject = [NSLock new];
    }

    // Making the lock async so it will not freeze the calling thread
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {

        [lockObject lock];

        NSURLComponents *components = [NSURLComponents componentsWithString:SINGULAR_API_URL];

        NSString* bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];

        components.queryItems = @[
            [NSURLQueryItem queryItemWithName:@"a" value:@"YOUR_SDK_KEY"],
            [NSURLQueryItem queryItemWithName:@"i" value:bundleIdentifier],
            [NSURLQueryItem queryItemWithName:@"app_v" value:@"YOUR_APP_VERSION"],
            [NSURLQueryItem queryItemWithName:@"n" value:eventName],
            [NSURLQueryItem queryItemWithName:@"p" value:@"iOS"],
            [NSURLQueryItem queryItemWithName:@"idfv" value:@"YOUR_IDFV"],
            [NSURLQueryItem queryItemWithName:@"idfa" value:@"YOUR_IDFA"],
            [NSURLQueryItem queryItemWithName:@"conversion_value"  value:[[SKANSnippet getConversionValue] stringValue]],
            [NSURLQueryItem queryItemWithName:@"total_revenue_by_currency"  value:[SKANSnippet dictionaryToJsonString:[SKANSnippet getTotalRevenue]]],
            [NSURLQueryItem queryItemWithName:@"first_call_to_skadnetwork_timestamp"  value:[NSString stringWithFormat:@"%ld", [SKANSnippet getFirstSkanCallTimestamp]]],
            [NSURLQueryItem queryItemWithName:@"last_call_to_skadnetwork_timestamp"  value:[NSString stringWithFormat:@"%ld", [SKANSnippet getLastSkanCallTimestamp]]],
        ];

        [[[NSURLSession sharedSession] dataTaskWithURL:components.URL
                                     completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                [lockObject unlock];
                if (handler) {
                    handler(-1, error);
                }
                return;
            }

            NSDictionary* parsedResponse = [SKANSnippet jsonDataToDictionary:data];

            if (!parsedResponse) {
                [lockObject unlock];
                if (handler) {
                    handler(-1, [NSError errorWithDomain:bundleIdentifier
                                                    code:0
                                                userInfo:@{NSLocalizedDescriptionKey:@"Failed parsing server response"}]);
                }
                return;
            }

            NSNumber *conversionValue = [parsedResponse objectForKey:@"conversion_value"];

            if (!conversionValue) {
                [lockObject unlock];
                NSString *status = [parsedResponse objectForKey:@"status"];

                if (!status || ![status isEqualToString:@"ok"]) {
                    if (handler) {
                    NSString *reason = [parsedResponse objectForKey:@"reason"];
                        if (!reason) {
                            reason = @"Got error from server";
                        }

                        handler(-1, [NSError errorWithDomain:bundleIdentifier
                                                        code:0
                                                    userInfo:@{NSLocalizedDescriptionKey:reason}]);
                    }
                }
                return;
            }

            NSNumber* currentValue = [SKANSnippet getConversionValue];
            if ([conversionValue intValue] <= [currentValue intValue]) {
                [lockObject unlock];
                return;
            }

            [SKANSnippet setConversionValue:[conversionValue intValue]];
            [SKANSnippet setLastSkanCallTimestamp];

            if (![SKANSnippet getFirstSkanCallTimestamp]) {
                [SKANSnippet setFirstSkanCallTimestamp];
            }

            [lockObject unlock];

            if (handler) {
                handler([conversionValue intValue], error);
            }
        }] resume];
    });
}

+ (BOOL)isSkanUpdateWindowOver {
    NSInteger timeDiff = [SKANSnippet getCurrentUnixTimestamp] - [SKANSnippet getLastSkanCallTimestamp];
    return SECONDS_PER_DAY <= timeDiff;
}

@end

ユーティリティメソッド

メタデータ管理

ユーティリティメソッドは、UserDefaults への SKAdNetwork メタデータの保存を管理し、 conversion value の計算に不可欠です。

重要な実装: これらのユーティリティは、正確な conversion value 計算に 必要なメタデータを維持します。必要でない限り 変更しないでください。

Utility Methods
+ (NSInteger)getFirstSkanCallTimestamp {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    return [userDefaults integerForKey:FIRST_SKAN_CALL_TIMESTAMP];
}

+ (NSInteger)getLastSkanCallTimestamp {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    return [userDefaults integerForKey:LAST_SKAN_CALL_TIMESTAMP];
}

+ (NSDictionary*)getTotalRevenue {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    if (![userDefaults objectForKey:TOTAL_REVENUE_BY_CURRENCY_KEY]) {
        return [NSDictionary new];
    }
    return [userDefaults objectForKey:TOTAL_REVENUE_BY_CURRENCY_KEY];
}

+ (NSInteger)getCurrentUnixTimestamp {
    return [[NSDate date]timeIntervalSince1970];
}

+ (void)setConversionValue:(int)value {
    if (@available(iOS 14.0, *)) {
        if (value <= [[SKANSnippet getConversionValue] intValue]) {
            return;
        }

        [SKAdNetwork updateConversionValue:value];

        NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
        [userDefaults setInteger:value forKey:CONVERSION_VALUE_KEY];
        [userDefaults synchronize];
    }
}

+ (void)setFirstSkanCallTimestamp {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setInteger:[SKANSnippet getCurrentUnixTimestamp] 
forKey:FIRST_SKAN_CALL_TIMESTAMP]; [userDefaults synchronize]; } + (void)setLastSkanCallTimestamp { NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setInteger:[SKANSnippet getCurrentUnixTimestamp]
forKey:LAST_SKAN_CALL_TIMESTAMP]; [userDefaults synchronize]; } + (void)setTotalRevenue:(NSDictionary *)values { NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setObject:values forKey:TOTAL_REVENUE_BY_CURRENCY_KEY]; [userDefaults synchronize]; } + (NSString*)dictionaryToJSONString:(NSDictionary*)dictionary { if (!dictionary || [dictionary count] == 0){ return @"{}"; } NSError *error; NSData *JSONData = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error]; if (error || !JSONData) { return @"{}"; } return [[NSString alloc] initWithData:JSONData
encoding:NSUTF8StringEncoding]; } + (NSDictionary*)JSONDataToDictionary:(NSData*)JSONData { if (!JSONData) { return nil; } NSError * error; NSDictionary * parsedData = [NSJSONSerialization
JSONObjectWithData:JSONData options:kNilOptions error:&error]; if (error || !parsedData) { return nil; } return parsedData; } @end

S2S 連携の更新

実装の検証とトラブルシューティングのために、任意で server-to-server 連携を SKAdNetwork メタデータで強化します。

必須の更新

conversion value のレスポンス

コンバージョンモデルが有効化されると、S2S エンドポイントは、クライアントサイドの 更新用の次の値を含む conversion_value 整数フィールドを返します。

レスポンスの処理: S2S エンドポイントのレスポンスから conversion_value を解析し、クライアントサイドコードを介して SKAdNetwork フレームワークに適用します。


任意の拡張

メタデータパラメータ

連携を検証し、実装の問題をトラブルシューティングするために、S2S セッションおよびイベントの リクエストを SKAdNetwork メタデータでエンリッチします。

パラメータ 説明
skan_conversion_value リクエスト時点での最新の conversion value
skan_first_call_timestamp SKAdNetwork API への初回呼び出しの Unix タイムスタンプ
skan_last_call_timestamp SKAdNetwork API への最新の呼び出しの Unix タイムスタンプ

メタデータ抽出コード

S2S 送信のために、ユーティリティメソッドを使用して SKAdNetwork メタデータ値を抽出します。

Objective-C
NSDictionary *skanMetadata = @{
    @"skan_conversion_value":
        [[SKANSnippet getConversionValue] stringValue],
    @"skan_first_call_timestamp":
        [NSString stringWithFormat:@"%ld", [SKANSnippet getFirstSkanCallTimestamp]],
    @"skan_last_call_timestamp":
        [NSString stringWithFormat:@"%ld", [SKANSnippet getLastSkanCallTimestamp]]
};

// Forward skanMetadata to your server for S2S API enrichment

パラメータをサーバーサイドに転送し、Singular の S2S API リクエストに付加します。 完全なパラメータのドキュメント: S2S API Reference


連携フロー

クライアントサイドの conversion value 管理からポストバック処理まで、 S2S を利用する顧客向けの完全な SKAdNetwork フロー。

SKAdNetwork S2S 連携フロー

フローの各段階

エンドツーエンドのプロセス

  1. conversion value のリクエスト: アプリコードが Singular エンドポイントと同期的に通信し、セッション、イベント、収益に基づく 最新の conversion value を取得します
  2. フレームワークの更新: アプリが受け取った conversion value で SKAdNetwork フレームワークを更新します
  3. メタデータのエンリッチメント: アプリが検証のために S2S イベントおよび セッションを SKAdNetwork メタデータでエンリッチします
  4. タイマーの満了: 最後の更新から 24 時間以上経過後、 SKAdNetwork が広告ネットワークにポストバックを送信します
  5. ポストバックの転送: ネットワークがポストバックを Singular に転送します (セキュアセットアップまたは通常)
  6. ポストバックの処理: Singular は次の方法でポストバックを 処理します:
    • 暗号署名を検証する
    • 設定されたモデルを使用して conversion value をデコードする
    • パートナー連携に基づいてマーケティングパラメータでエンリッチする
    • デコードされたデータをポストバック経由で BI とパートナーに送信する

データの分離: SKAdNetwork データ (インストールおよび デコードされたイベント) は、別個のレポート、API、ETL テーブル、 ポストバックを介してアクセスでき、テストおよび検証中に既存のデータセットと 混ざるのを防ぎます。


アプリライフサイクルの実装

完全なアトリビューションのカバレッジのために、適切なアプリライフサイクルのポイントで SKAdNetwork メソッドを組み込みます。

実装例

メソッドの配置

Objective-C
// On app launch (in applicationDidFinishLaunching)
[SKANSnippet registerAppForAdNetworkAttribution];

// After each session handled
[SKANSnippet updateConversionValueAsync:^(int value, NSError *error) {
    if (error) {
        NSLog(@"Conversion value update failed: %@", error);
    } else {
        NSLog(@"Conversion value updated to: %d", value);
    }
}];

// After handling non-revenue events
[SKANSnippet updateConversionValueAsync:@"event_name"
                   withCompletionHandler:^(int value, NSError *error) {
    if (error) {
        NSLog(@"Event conversion value update failed: %@", error);
    } else {
        NSLog(@"Event conversion value updated to: %d", value);
    }
}];

// After handling revenue events
[SKANSnippet updateRevenue:15.53 andCurrency:@"USD"];
[SKANSnippet updateConversionValueAsync:@"revenue_event_name"
                   withCompletionHandler:^(int value, NSError *error) {
    if (error) {
        NSLog(@"Revenue conversion value update failed: %@", error);
    } else {
        NSLog(@"Revenue conversion value updated to: %d", value);
    }
}];

実装のテスト

本番環境へのデプロイ前に、Singular のテストエンドポイントを使用して SKAdNetwork の実装を検証します。

テスト環境

テストエンドポイントの設定

アプリのフローをシミュレートするために、本番用のコンバージョン管理エンドポイントを テストエンドポイントに置き換えます。

テストエンドポイント URL:

https://skadnetwork-testing.singular.net/api/v1/conversion_value
  • テストエンドポイントには API キーは不要です
  • conversion value を 0~2 の順に返します
  • 3 回目の呼び出し以降は空文字列を返します

テスト手順

検証ステップ

  1. エンドポイントの更新: SINGULAR_API_URL 定数をテストエンドポイント URL に置き換えます
  2. conversion value の初期化: デフォルトの conversion value が 0 に初期化されていることを確認します
  3. イベントの生成: アプリ内で 3 つの異なるイベントを トリガーします
  4. 更新の確認: 各イベント後に updateConversionValueAsync が呼び出されることを確認します
  5. 値のログ記録: 返された conversion value をログに記録し、 正しい進行 (0 → 1 → 2) を確認します
  6. 完了の確認: 3 回目の呼び出し後、空の レスポンスと最終値の保持を確認します

期待される挙動:

  • 1 番目のイベント: conversion value 0 を受け取る
  • 2 番目のイベント: conversion value 1 を受け取る
  • 3 番目のイベント: conversion value 2 を受け取る
  • 4 番目以降のイベント: 空のレスポンス、値 2 が維持される

コードの変更履歴

これまでに適用されたコードサンプルの更新と重要な修正を記録します。

バージョン履歴

日付 変更内容
2020年10月1日
  • 修正 [CRITICAL]: getConversionValueFromServer の初期化における ロック機構を修正しました
  • 改善: サーバーレスポンスの失敗理由が エラー処理で返されるようになりました
2020年9月23日
  • 修正 [CRITICAL]: setConversionValue が正しく updateConversionValue を呼び出すようになりました
  • 改善: サーバーから conversion value を 取得する際のエラー処理を強化しました
2020年9月15日
  • 変更 [IMPORTANT]: getConversionValue のデフォルト値が、未設定時に null から 0 に変更されました
  • 改善: 収益のレポートおよび 処理を強化しました
  • 改善: 非同期の conversion value 取得の実装