どのようにお手伝いできますか?

クリック署名:アドネットワーク向け

  • 更新

概要:概要:クリックに署名の検証を追加することで、広告不正による被害を回避することが可能です。これにより、不正なクリックがアドネットワークに紐付けられなくなります。 

クリック署名について

不正業者は、ごくわずかな技術でアドネットワークに代わって、数千はたまた数百万もの偽クリックを作成し、AppsFlyerへ送信することが可能です。アドネットワーク自身がこのような問題が発生していることを認識できていない場合もあります。 

AppsFlyerによって媒体へ紐付けられたクリックが、不正業者によって作られた偽クリックではなく実際にアドネットワークから発生したものであることを確実にするために、HMAC-SHA256の署名を使ってクリックに署名を付与することをおすすめします。

この署名によりAppsFlyerはクリックを検証でき、クリック情報が不正業者によって操作されていないことを確認します。

  • 検証されたクリックは記録され、アドネットワークへ紐付けられます。 
  • 無効になったクリックは除外され、以下のように処理されます: 
    • 詳細はこちら
    • アドネットワークのコンバージョン率やクリック洪水のしきい値には影響を与えません。

クリック署名の連携

フロー

以下のチャートでは、初期の開発と基本テストから実稼働テスト、そして最終的な実稼働までの流れの概要を示しています。 

Click_signing_integration_flow.png

手順

前提条件:

クリックに署名するには:

  1. シークレットキーの生成APIを使用して、シークレットキーを生成してください。
    ベストプラクティス:推奨:24時間毎に新しいシークレットキーを生成して使用してください。シークレットキーの有効期限は36時間です。  

  2. 以下のようにコードをクリックURLに追加してください:  
    • expiresのパラメータにはアドネットワークがクリックを主張しなくなるUnixのタイムスタンプを含めてください。
    • HMAC-SHA256の署名情報を含めてください。
      例:
      https://app.appsflyer.com/com.app.id?pid=adnetwork_int&c=my_campaign&clickid=sdkfjasksjskdfj9845weh&af_siteid=12345&expires=1597657118&signature_v2=8fnDVzZP_WRZnv3KNJaREOEfvB5p9oRc_XlKEvUo8gk

注

 

順序 パラメータ 必須 備考
1 link_domain はい
  • app.appsflyer.com
  • myapp.onelink.me
  • click.mycustomdomain.com
2 link_path はい
3 pid はい  
4 af_prt いいえ  
5 af_siteid はい  
6 clickid はい
7 &expires= はい
8 af_engagement_type いいえ  
9 af_click_lookback いいえ  
10 af_viewthrough_lookback= いいえ  
11 af_reengagement_window いいえ  
12 is_retargeting いいえ  
13 af_ip いいえ  
14 advertising_id いいえ  
15 oaid いいえ  
16 fire_advertising_id いいえ  
17 idfa いいえ  
18 idfv いいえ  


    • 例: 
      [["key-1","value-1"],["key-2","value-2"]...["key-n","value-n"]]

 

//generate a signature from the click url and encode it with base64 without padding String generatedSignature = Base64.getUrlEncoder().withoutPadding().encodeToString(mac.doFinal(jsonString.getBytes()));

https://yourbrand.onelink.me/qsWL?pid=mediasource_int&advertising_id=12345678-1234-1234-1234-123456789012
&af_ad_type=video&af_adset=MMP&clickid=sdkfjasksjskdfj9845weh&af_siteid=my_site&af_viewthrough_lookback=2h&c=my_campaign
&expires=1689695615

[
	["link_domain","yourbrand.onelink.me"],
	["link_path","qswl"],
	["pid","mediasource_int"],
	["af_siteid","my_site"],
	["clickid","12345"],
	["expires","1689695615"]
]

[["link_domain","yourbrand.onelink.me"],["link_path","qswl"],["pid","mediasource_int"],["template-id","qswl"],["af_siteid","my_site"],["clickid","12345"],["expires","1689695615"]]

https://yourbrand.onelink.me/qsWL?pid=mediasource_int&advertising_id=12345678-1234-1234-1234-123456789012
&af_ad_type=video&af_adset=MMP&af_siteid=my_site&af_viewthrough_lookback=2h&c=my_campaign
&expires=1689695615
&signature_v2=WIfCmfLAPSsVrBTqCqfihMeLCnbE4dIAlhHF84WsiWA

コードの例

signing-supported-params.json

[
  {"index":1, "name":"pid", "mandatory":true},
  {"index":2, "name":"af_prt", "mandatory":false},
  {"index":3, "name":"af_siteid", "mandatory":true},
  {"index":4, "name":"clickid", "mandatory":true},
  {"index":5, "name":"expires", "mandatory":true},
  {"index":6, "name":"af_engagement_type", "mandatory":false},
  {"index":7, "name":"af_click_lookback", "mandatory":false},
  {"index":8, "name":"af_viewthrough_lookback", "mandatory":false},
  {"index":9, "name":"af_reengagement_window", "mandatory":false},
  {"index":10, "name":"is_retargeting", "mandatory":false},
  {"index":11, "name":"af_ip", "mandatory":false},
  {"index":12, "name":"advertising_id", "mandatory":false},
  {"index":13, "name":"oaid", "mandatory":false},
  {"index":14, "name":"fire_advertising_id", "mandatory":false},
  {"index":15, "name":"idfa", "mandatory":false},
  {"index":16, "name":"idfv", "mandatory":false}
]

signature-example.go

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/url"
	"os"
	"strings"
	"time"
)

type Param struct {
	Name      string
	Mandatory bool
}

func LoadSupportedParams() ([]Param, error) {
	path := "./signing-supported-params.json"
	jsonFile, _ := os.Open(path)
	defer jsonFile.Close()
	byteValue, err := io.ReadAll(jsonFile)
	if err != nil {
		return nil, errors.New("failed loading " + path)
	}
	var params []Param
	_ = json.Unmarshal(byteValue, &params)
	return params, nil
}

func computeHmac256(message, secret string) (res string, errResult error) {
	defer func() {
		if r := recover(); r != nil {
			errResult = r.(error)
			fmt.Println("failed to invoke ComputeHmac256. err: %+v", errResult)
			res = ""
			return
		}
	}()

	key := []byte(secret)
	h := hmac.New(sha256.New, key)
	h.Write([]byte(message))
	return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(h.Sum(nil)), nil
}

// this function accepts a click url, a ttl for the click in seconds, and the list of supported params
// the function:
// 1. adds the expiration to the click based on the provided ttl
// 2. builds a json for the signature
// 3. create an HMAC256 signature
// 4. adds the signature to the click url
func signURLV2(originalURL string, secret string, clickTtlSeconds int64, supportedParams []Param) (signedURL string, err error) {

	//add an expiration to the click
	expires := time.Now().UTC().Unix() + clickTtlSeconds
	urlWithExpired := fmt.Sprintf("%s%s%d", originalURL, "&expires=", expires)

	//parse the url
	parsedURL, err := url.Parse(urlWithExpired)
	if err != nil {
		return "", errors.New("failed parsing URL")
	}

	//build a json from the url
	jsonStr, err := buildJSONFromURLV2(parsedURL, supportedParams)
	if err != nil {
		return "", err
	}

	//create a signature
	signatureV2, err := computeHmac256(jsonStr, secret)
	if err != nil {
		fmt.Println("Failed to computeHMAC256 for url %s. err: %+v", jsonStr, err)
		return "", errors.New("fail to computeHMAC256 for url")
	}

	//add the signature to the url
	signedURL = fmt.Sprintf("%s%s%s", urlWithExpired, "&signature_v2=", signatureV2)

	return signedURL, nil

}

// this function builds a json from a given click url.
// It returns a string representation of the json ready to be signed.
func buildJSONFromURLV2(parsedURL *url.URL, supportedParams []Param) (string, error) {
	//initiate an empty json in the structure [[key-1,val-1],[key-2,val-2]...[key-n,val-n]]
	var jsonData [][2]string

	//add the url host domain to the json
	domain := parsedURL.Host
	param := [2]string{"link_domain", domain}
	jsonData = append(jsonData, param)

	//add the path (app-id or template-id) to the json
	path := parsedURL.Path
	if len(path) > 1 {
		param := [2]string{"link_path", path[1:]}
		jsonData = append(jsonData, param)
	}

	//loop over the ordered list of supported parameters and add them to the json
	for i := 0; i <len(supportedParams); i++ {name := supportedParams[i].Name val := parsedURL.Query().Get(name) if len(val) > 0 {
			param := [2]string{name, val}
			jsonData = append(jsonData, param)
		} else if supportedParams[i].Mandatory {
			return "", errors.New("missing mandatory param: " + name)
		}
	}

	//generate string representation of the json object
	jsonObj, _ := json.Marshal(jsonData)
	return strings.ToLower(string(jsonObj)), nil
}

func main() {

	supportedParams, err := LoadSupportedParams()
	if err != nil {
		fmt.Println("Error: ", err)
		os.Exit(1)
	}

	var secretKey = "tqJU4Qd/eFTEWfqW7KCG9asDO0bmZoFzv8GY3VPSPAM="
	var clickTtlSeconds int64 = 60
	var originalUrl = "https://yourbrand.onelink.me/qsWL?pid=mediasource_int&advertising_id=12345678-1234-1234-1234-123456789012&clickid=1234&af_ad_type=video&af_adset=MMP&af_siteid=my_site&af_viewthrough_lookback=2h&c=my_campaign"

	fmt.Println("Original URL: ", originalUrl)

	signedUrl, err := signURLV2(originalUrl, secretKey, clickTtlSeconds, supportedParams)
	if err != nil {
		fmt.Println("Error: ", err)
		os.Exit(1)
	} else {
		fmt.Println("Signed URL: ", signedUrl)
	}
}

クリック署名の各種API

AppsFlyerでは、アドネットワークがクリック署名のプロセルを管理できる各種APIを用意しています。以下表のAPI一覧と、APIの利用に必要な各種情報をまとめた各セクションを参照してください。 

クリック署名の各種API

APIメソッド 備考
Generate secret key 署名に使用するシークレットキーを生成します。
Revoke secret key シークレットキーを無効化します。
Test 署名をテストするために単発のクリックを送信します。
Configure mode クリック署名の各モードを設定します: 
  • 無効 (デフォルト設定)
  • 有効enabled: AppsFlyerが無効な署名を持つクリック、または署名が欠損しているクリックを実際にブロックします。/strong>
  • 不可
Get configuration 有効なシークレットキーのIDと選択されているモードを取得します。
レポート システムがreport-onlyモードまたはenabledモードの場合、正常なクリックと失敗したクリックに関する統計情報を取得します。
Exclude app クリック署名のルールから除外するアプリIDを設定します。
Remove excluded app 一度除外した後に、再度クリック署名のルールに含めるアプリIDを設定します。

V1(レガシー)

Generate secret keyのメソッド

Generate secret keyの基本仕様

カテゴリ アイテム 説明
リクエスト HTTP メソッド POST
経路 https://hq1.appsflyer.com/api/p360-click-signing/secret?ttlHours=<ttlHours>
認証ヘッダー  
レスポンス 結果 JSON内でシークレットキーが返されます。
  リクエスト制限 一度に最大2つまでシークレットキーをアクティブにできます

API リクエスト

メソッド

POST https://hq1.appsflyer.com/api/p360-click-signing/secret?ttlHours=<ttlHours>

パラメーター

パラメータ 説明
ttlHours
  • デフォルト設定は36時間です。

JSON レスポンス

キー 説明
secret-key-id シークレットキーのIDです。
secret key クリック署名のシークレットキーです。
expiration ミリ秒単位のエポックタイムです。

Generate secret keyのcurlサンプルとレスポンス

Curl リクエスト

curl --location --request POST 'https://hq1.appsflyer.com/api/p360-click-signing/secret?ttlHours=36' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'

JSON レスポンス

{
	"secret-key-id": "59ad6547-affc-45eb-a6c9-9805f88ee755",
	"secret-key": "zGW6Rhrmb8+vuhHtL/Kp6rW5Ci9PNsjH1J5MGO9SIeg=",
	"expiration": 1610533263
}

HTTP のレスポンスコード

応答コード

コード  メッセージ 備考
200 OK  
401 許可されていません 認証ヘッダーが無効、または見つかりません。

Revoke secret keyのメソッド

Revoke secret keyの基本仕様

カテゴリ アイテム 説明
リクエスト HTTP メソッド DELETE
経路 https://hq1.appsflyer.com/api/p360-click-signing/secret/<secret-id>
認証ヘッダー  
レスポンス 結果 Empty

API リクエスト

メソッド

DELETE https://hq1.appsflyer.com/api/p360-click-signing/secret/<secret-id>

パラメーター

パラメータ 説明
secret-id 無効にするシークレットキーのIDです。

Generate secret keyのcurlサンプルとレスポンス

Curl リクエスト

curl --location --request DELETE 'https://hq1.appsflyer.com/api/p360-click-signing/secret/59ad6547-affc-45eb-a6c9-9805f88ee755' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'

HTTP のレスポンスコード

応答コード

コード  メッセージ 備考
200 OK  
401 許可されていません 認証ヘッダーが無効、または見つかりません。

Testのメソッド

Testの基本仕様

カテゴリ アイテム 説明
リクエスト HTTP メソッド POST
経路 https://hq1.appsflyer.com/api/p360-click-signing/test
認証ヘッダー  
レスポンス 結果 JSONで返されます。

API リクエスト

メソッド

POST https://hq1.appsflyer.com/api/p360-click-signing/test

パラメーター

パラメータ 説明
URL 署名を含む、テストで使用するクリックURLです。

JSON レスポンス

キー 説明
test-status Passed(成功) もしくは Failed(失敗)
メッセージ テストの失敗理由。例:
  • Missing signature (署名がありません。)
  • Invalid signature (署名が無効です。)
  • Expired  (署名が期限切れです。)

Testのcurlサンプルとレスポンス

Curl リクエスト

curl --location --request POST 'https://hq1.appsflyer.com/api/p360-click-signing/test' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}' \
--header 'Content-Type: application/json' \
--data-raw '{
   "url": "https://app.appsflyer.com/com.app.id?pid=adnetwork_int&c=my_campaign&clickid=sdkfjasksjskdfj9845weh&af_site_id=12345&expires=1597657118&signature=8fnDVzZP_WRZnv3KNJaREOEfvB5p9oRc_XlKEvUo8gk"
}'

JSON レスポンス

{
	"test-status":"Passed / Failed",
	"message": "Invalid signature"
}

HTTP のレスポンスコード

応答コード

コード  メッセージ 備考
200 OK  
401 許可されていません 認証ヘッダーが無効、または見つかりません。

Configure modeのメソッド

Configure modeの基本仕様

カテゴリ アイテム 説明
リクエスト HTTP メソッド POST
経路 https://hq1.appsflyer.com/api/p360-click-signing/config/mode/<mode>
認証ヘッダー  
レスポンス 結果 JSONで返されます。

API リクエスト

メソッド

POST https://hq1.appsflyer.com/api/p360-click-signing/config/mode/<mode>

パラメーター

パラメータ 説明
mode 含まれる値:
  • enabled
    ご注意ください
  • disabled
  • report-only

Configure modeのcurlサンプル

Curl リクエスト

curl --location --request POST 'https://hq1.appsflyer.com/api/p360-click-signing/config/mode/report-only' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'

HTTP のレスポンスコード

応答コード

コード  メッセージ 備考
200 OK  
400 Bad request Invalid mode (無効なモード)
401 許可されていません 認証ヘッダーが無効、または見つかりません。

カテゴリ アイテム 説明
リクエスト HTTP メソッド POST
経路 https://hq1.appsflyer.com/p360-click-signing/config/circuit-breaker
認証ヘッダー  
レスポンス 結果

API リクエスト

メソッド

POST https://hq1.appsflyer.com/p360-click-signing/config/circuit-breaker

パラメータ 説明
ステータス
  • enabled
  • disabled

Curl リクエスト

curl --location --request POST 'https://hq1.appsflyer.com/api/p360-click-signing/config/circuit-breaker' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'
--data-raw '{
"status":"enabled"
}'

HTTP のレスポンスコード

応答コード

コード  メッセージ 備考
200 OK  
400 Bad request
401 許可されていません 認証ヘッダーが無効、または見つかりません。

Get configurationのメソッド

Get configurationの基本仕様

カテゴリ アイテム 説明
リクエスト HTTP メソッド GET
経路 https://hq1.appsflyer.com/api/p360-click-signing/config
認証ヘッダー  
レスポンス 結果 JSONで返されます。

API リクエスト

メソッド

GET https://hq1.appsflyer.com/api/p360-click-signing/config

JSON レスポンス

キー 説明
mode 以下のいずれか:
  • enabled
  • disabled
  • report-only
circuit-breaker-config
  • enabled
  • disabled
     
active-key-ids アクティブなキーを保持したJSON配列です:
 
  • secret-key-id:
  • expirationexpiration: シークレットキーのミリ秒単位のエポック時間で表現された有効期限です。
excluded-app-ids 除外したアプリIDを含むJSON配列です。

Get configurationのcurlサンプルとレスポンス

Curl リクエスト

curl --location --request GET 'https://hq1.appsflyer.com/api/p360-click-signing/config' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'

JSON レスポンス

{
	"mode": "report-only",
	"active-key-ids": [
		{
			"secret-key-id": "59ad6547-affc-45eb-a6c9-9805f88ee755",
			"expiration": 1610533263
		}
	],
	"excluded-app-ids": [
		"app-id-1", 	"app-id-2"
	]

}

HTTP のレスポンスコード

応答コード

コード  メッセージ 備考
200 OK  
401 許可されていません 認証ヘッダーが無効、または見つかりません。

Reportのメソッド

Reportの基本仕様

カテゴリ アイテム 説明
リクエスト HTTP メソッド GET
経路 https://hq1.appsflyer.com/api/p360-click-signing/report
認証ヘッダー  
レスポンス 結果 CSVで返されます。

API リクエスト

メソッド

GET https://hq1.appsflyer.com/api/p360-click-signing/report

パラメーター

パラメータ 説明
start-date レポートの開始日時です。形式: yyyy-mm-ddThh
end-date レポートの終了日時です。形式:yyyy-mm-ddThh
API には、 start-dateとend-dateの両方を含めるか、もしくはどちらも入力しなくても機能します。start/end-dateが指定されていない場合、過去24時間の結果が表示されます。

CSVレスポンス

列 説明
time クリックの日時です。形式:yyyy-mm-ddThh
total_clicks レポート期間におけるクリック数の合計です。
valid_clicks レポート期間における有効なクリック数です。
missing_signature レポート期間における署名が欠損しているクリック数です。
expired_clicks レポート期間における署名の期限切れのクリック数です。
invalid_signature レポート期間における無効な署名のクリック数です。
no_active_secrets システム上に有効なシークレットキーがないために拒否されたクリック数です。(report-onlyモードの場合)

Testのcurlサンプルとレスポンス

Curl リクエスト

curl --location --request GET 'https://hq1.appsflyer.com/api/p360-click-signing/report?start-date=2021-01-07T07&end-date=2021-01-17T12' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}' \

CSVレスポンス

time total_clicks valid_clicks missing_signature expired_clicks invalid_signature no_active_signatures
2021-01-17T07 928082156 928082156  0  0  0  0
2021-01-17T08 923796132 923796132  0  0  0  0
2021-01-17T09 917541373 917541373  0  0  0  0
2021-01-17T10 909977064 909977064  0  0  0  0
2021-01-17T11 965104299 965104299  0  0  0  0
2021-01-17T12 975134824 975134824  0  0  0  0

HTTP のレスポンスコード

応答コード

コード  メッセージ 備考
200 OK  
401 許可されていません 認証ヘッダーが無効、または見つかりません。

Exclude appのメソッド

Exclude appの基本仕様

カテゴリ アイテム 説明
リクエスト HTTP メソッド POST
経路 https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/<app-id>
認証ヘッダー  
レスポンス 結果 Empty

API リクエスト

メソッド

POST https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/<app-id>

パラメーター

パラメータ 説明
app-id クリック署名の検証対象から除外するアプリIDです。

Exclude appのcurlサンプル

Curl リクエスト

curl --location --request POST 'https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/appname.com' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'

HTTP のレスポンスコード

応答コード

コード  メッセージ 備考
200 OK  
401 許可されていません 認証ヘッダーが無効、または見つかりません。

Remove excluded appのメソッド

Remove excluded appの基本仕様

カテゴリ アイテム 説明
リクエスト HTTP メソッド DELETE
経路 https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/<app-id>
認証ヘッダー  
レスポンス 結果 Empty

API リクエスト

メソッド

DELETE https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/<app-id>

パラメーター

パラメータ 説明
app-id クリック署名の検証対象から一度除外し、改めてそのリストから削除するアプリIDです。

Remove excluded appのcurlサンプル

Curl リクエスト

curl --location --request DELETE 'https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/appname.com' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'

HTTP のレスポンスコード

応答コード

コード  メッセージ 備考
200 OK  
401 許可されていません 認証ヘッダーが無効、または見つかりません。

Codeサンプル

package sign;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

.
.
.

String clickUrl = "https://app.appsflyer.com/com.app.id?pid=adnetwork_int&c=my_campaign&clickid=sdkfjasksjskdfj9845weh&af_site_id=12345";
String secretKey = "secret_key"; 
int ttlMinutes = 5;


//add expiration to the click URL
long expiration = System.currentTimeMillis() + (60000L * ttlMinutes);
clickUrl += "&expires="+expiration;

//create a SecretKey object from the given key string
SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);

//generate a signature from the click url and encode it with base64 without padding
String generatedSignature =     
Base64.getUrlEncoder().withoutPadding().encodeToString(mac.doFinal(clickUrl.getBytes()));

//add the signature to the click URL
String signedClickUrl = clickUrl + "&signature=" + generatedSignature;

追加情報

トラブルシューティング

よくある質問

Q:

A:

Q:

A: APIトークン

Secret Key

Q:

A:

特性

特性 説明
クリック署名 署名はアドネットワークのサーバー上で生成する必要があります。
Secret Key
  • 広告ネットワークでは、最大2つまでシークレットキーを同時にアクティブにすることが可能です。
  • 個々のシークレットキーには有効期限があります。
  • 期限切れのシークレットキーで署名されたクリックは除外されます。
Report API クリックの有効性の更新は、時間単位で集計されます。