概要:本文讲解了如何从广告主服务器向AppsFlyer发送事件数据,以衡量在应用外部发生的移动事件。
适用于移动设备的服务器到服务器事件API
对于iOS 14以上的应用版本,您需要发送OS(操作系统)参数。
AppsFlyer平台会对由AppsFlyer SDK和API发送的移动应用事件进行归因和记录,因此您可以使用S2S API报告应用程序外部发生的事件(如用户使用您的Web界面进行续订)。记录到的S2S事件会反映在AF后台的所有面板/报告中,包括控制面板,原始数据和分析报告。另请参阅适用于PBA的网页S2S,了解有关PBA网页事件的详情。
AppsFlyer会将以下信息填充到S2S事件中:
- S2S消息中发送的值
- AppsFlyer的部分归因信息,例如激活时间和媒体渠道。
API使用说明
请根据以下各章节中的信息创建API调用。
关于S2S API的基础知识
API端点(endpoint) |
|
HTTP方法 | POST |
内容类型要求 | application/ json |
授权 |
|
为服务器加白以获取响应消息 |
如果您的服务器设置了防火墙,请务必将appsflyer.com的域名加白,否则可能会收不到响应消息。 |
JSON的payload限制 |
JSON的payload大小不能超过1KB |
拉取频次限制 |
POST限制数量:每分钟60,000次。如需提高限额,请联系您的CSM。 |
编码说明 |
|
编码URLs |
在生成最终的URL之前,请将所有的保留字符进行URL编码(百分比编码)处理 (https://tools.ietf.org/html/rfc3986#section-2.1 )。 |
TLS |
请使用TLS 1.2或更高版本。详见适用密码。 |
Payload参数
- Payload(关键信息)参数包含一个或多个设备标识符(具体取决于操作系统)。
-
如果无法发送设备标识符怎么办?
- 有时可能会出于一些广告主不可控的因素而导致标识符无法发送,比如用户选择了限制广告追踪(Limit ad tracking,简称LAT)或者用户在iOS 14+系统中拒绝授权追踪。出现这种情况时,如果IDFV可用,则请发送IDFV。
- 如果不发送广告ID/设备ID会导致下列问题:
- 回传问题:媒体渠道会收到回传,但其中没有设备ID,因此无法关联到用户。
- 受众细分及规则失效:受众细分规则组中需要用到标识符,请根据您的规则组中使用的ID类型来发送设备ID或客户用户ID(CUID)。
操作系统 | 标识符名称 | 描述 |
---|---|---|
iOS
|
idfa |
如可获取,此处填充设备IDFA 格式:字符串 例如: |
idfv |
如可获取,此处填充设备IDFA 格式: 字符串 |
|
Android |
advertising_id |
如可获取,此处填充设备GAID(广告ID) 格式:字符串 示例: |
oaid |
格式:字符串 例如: |
|
amazon_aid |
格式:字符串 |
|
imei |
格式:字符串 例如: |
参数 | 是否必须配置 | 描述 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
appsflyer_id |
是 |
应用首次启动时由AppsFlyer生成的非重标识符。
|
|||||||||
CUSTOMER_USER_ID |
否 |
客户用户ID(CUID), 由广告主设置的非重用户标识符。
|
|||||||||
att |
否 |
注意! 建议以ATTrackingManager的值来填充 |
|||||||||
ip |
否 |
事件发生期间的移动设备的IP地址。
|
|||||||||
弃用 |
|
||||||||||
eventName |
是 |
请指定事件名称,并确保事件名称符合营销人员的要求。
|
|||||||||
eventValue |
是 |
如果要发送不带值的事件,请发送:
|
|||||||||
app_version_name |
否 |
您的应用版本或标识符。
|
|||||||||
app_store |
否 |
相当于安卓应用中的AF_STORE,用于说明用户是从哪个应用商店下载的应用。
|
|||||||||
eventTime |
否 |
即事件发生的时间,以UTC时间显示。
一天结束:
示例
|
|||||||||
eventCurrency |
否 |
ISO 4217中的3字符代码和BCN(比特币)
|
|||||||||
bundleIdentifier |
否* |
非重应用标识符。在原始数据中,该参数会填充Bundle ID字段。请务必填充该参数,因为许多广告平台都要求有该参数才能进行投放优化。
|
|||||||||
sharing_filter |
否 |
sharing filter(数据分流)用于屏蔽对接渠道和其他第三方通过回传/API共享S2S事件数据。 该参数可帮助您满足GDPR和CCPA等法规要求、在用户拒绝授权时确保合规,同时应对其他的业务逻辑。 Sharing_filter具有以下选项:
如果需要渠道ID列表,请联系您的CSM或AppsFlyer技术支持。 |
|||||||||
custom_dimension |
否 |
AppsFlyer留作后用 |
|||||||||
app_type |
否 |
适用于iOS应用。 允许的值: 请仅针对app clip中发生的事件发送该参数。 |
|||||||||
custom_data |
否 |
用于向AppsFlyer发送自定义数据,类似于使用 在原始数据中:填充 格式为JSON,具体见下图示例。 示例:
|
|||||||||
OS |
否** |
设备的操作系统版本。不管您的应用在哪个操作平台中运行,都请务必配置该参数。 格式:字符串 示例: 注意! 从2021年7月1日起,对于iOS应用,如果广告主不发送该参数,AF会默认设备的操作系统版本为iOS 14.5。 |
|||||||||
aie |
否 |
该参数用于判断用户是否拒绝共享其广告ID。 该字段适用于安卓(所有版本)或iOS 14以下的设备。请注意:对于iOS 14.5+,请使用att参数。 该字段可能出现的值为:
|
|||||||||
*许多广告平台要求有该参数才能进行投放优化。 **请务必发送该参数,以确保数据处理无误。我们没有将其标记为必须配置字段是出于向下兼容性的考虑。 |
Curl示例
curl --location --request POST 'https://api2.appsflyer.com/inappevent/<app_id_placeholder>'\
--header 'authentication: <dev_key_placeholder>
' \
--header 'Content-Type: application/json' \
--data-raw '{
"appsflyer_id": "9999999999999-9999999999999999999",
"advertising_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"customer_user_id" : "example_customer_id_123",
"ip": "199.0.2.1",
"app_version_name" : "example_version_name",
"eventTime" : "2020-02-25 12:00.000",
"eventName": "af_purchase",
"eventCurrency": "ZAR",
"os" : "14.6",
"att" : 3,
"eventValue":
"{
\"af_revenue\": \"1006\",
\"af_content_type\": \"wallets\",
\"af_content_id\": \"15854\",
\"af_quantity\" :\"1\"
}"
}
'
响应代码
响应代码 | 信息 | 如何处理 |
---|---|---|
200 | OK |
接收消息时所做的数据验证是比较有限的,因此即使事件没有完全录入AppsFlyer服务器,您也会收到OK的响应消息。请按以下步骤调试事件:
|
400 | Failed to Authenticate (无法进行身份验证) |
请确保身份验证密钥(authentication key)正确无误。 |
400 | appsflyer_id is a mandatory field (appsflyer_id是必填字段) |
|
401 | Unauthorized (未授权) |
authentication header中提供的密钥不是此应用的开发者秘钥(dev_key)。 |
400 | Bad request (错误的请求) |
只要有一项验证标准不符合,就会导致请求失败。 |
400 | Payload is missing or failed to parse (Payload缺失或解析失败) |
|
500 | Internal Server Error (内部服务器错误) |
请确保JSON是字符串格式且格式正确。 |
测试
注意事项:
- 在测试时,请使用非自然激活的AppsFlyer ID,这样AF就能对测试事件进行实时归因。自然事件的归因会有几个小时的延迟。
- 事件的某些字段是根据用户的激活归因事件填充的,如激活时间和媒体渠道。
请按以下步骤在测试设备上完成非自然激活:
- 请准备一条自定义归因链接,用于测试S2S消息。在该链接中,将媒体渠道参数设置为s2s_test,并按归因链接示例中的代码设置广告ID(GAID、IDFA等)。
- 注册测试设备
- 从测试设备上卸载应用程序。
- 将链接发送到测试设备。
- 点击链接
- 安装并启动该应用程序。
- 进入AF面板检查该激活是否已被归因。
- 提取AppsFlyer ID,在S2S消息中会用到该信息。
请按以下步骤发送S2S测试消息 :
- 使用上一步中获取的AppsFlyer ID准备S2S消息。具体的代码示例请参见下文相关部分。
- 发送消息。
- 请执行以下任一项操作,以查看AppsFlyer是如何记录该消息的:
- 查看报告并注意以下几点:
- S2S消息中发送的值正确填充了报告。请特别注意Event date(事件日期)、Event currency(事件货币)、Event revenue(事件收入)和Event value(事件值)这几个字段。
- 归因渠道和激活时间由AppsFlyer填充。
- SDK本身提供的值(例如SDK版本)不会被填充。
归因链接示例
Android
https://app.appsflyer.com/<app_id>?pid=s2s_test&c=test&advertising_id=<GAID>
iOS
https://app.appsflyer.com/<app_id>?pid=s2s_test&c=test&idfa=<IDFA>
发送S2S消息的示例代码
/* using the okhttp package
install from maven https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp*/
import okhttp3.*;
import java.io.IOException;
public class SendRequest {
public static void main(String[] args) {
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType," +
"{\r\n\t\"appsflyer_id\": \"<APPS_FLYER_ID>\"," +
"\r\n\t\"customer_user_id\": \"123456\",\r\n\t\"eventName\": \"af_purchase\",\r\n\t\" +
"eventValue\": \"{\\\"af_revenue\\\":\\\"6\\\" ,\\\"af_content_id\\\":\\\"15854\\\"}\",\r\n\t\" +
"af_currency\": \"USD\",\r\n\t\" +
"eventTime\": \"2018-08-10 4:17:00.000\",\r\n\t\");
Request request = new Request.Builder()
.url("https://api2.appsflyer.com/inappevent/<APP_ID>")
.post(body)
.addHeader("Content-Type","application/json")
.addHeader("authentication","<YOUR_DEV_KEY>")
.build();
try {
Response response = client.newCall(request).execute();
System.out.println(response.code());
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
''' using the requests python package, install using pip install requests '''
import requests
url = "https://api2.appsflyer.com/inappevent/[Insert app ID here]"
payload = "{\r\n \"appsflyer_id\": \"9999999999999999999999\",\r\n\t\"IDFA\":\"999999999999999999999999\",\r\n\t\"customer_user_id\" : \"14mar\",\r\n\t\"ip\": \"10.0.0.1\",\r\n\t\"app_version_name\" : \"example_version_name\",\r\n\t\"eventTime\" : \"2020-04-25 08:59:01.23\",\r\n\t\"eventName\": \"gaf_purchase\",\r\n\t\"eventCurrency\": \"ZAR\",\r\n\t\"eventValue\": \r\n\t\"{\r\n\t\t\\\"af_revenue\\\": \\\"1000\\\",\r\n\t\t\\\"af_content_type\\\": \\\"wallets\\\",\r\n\t\t\\\"af_content_id\\\": \\\"15854\\\",\r\n\t\t\\\"af_quantity\\\" :\\\"1\\\"\r\n }\"\r\n}"
headers = {
'authentication': '[Insert dev key]',
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data = payload)
print(response.text.encode('utf8'))
/* using the request npm package, install using npm install request */
var request = require("request");
var options = { method: 'POST',
url: 'https://api2.appsflyer.com/inappevent/<APP_ID>',
headers:
{
"authentication": '<YOUR_DEV_KEY>',
'Content-Type': 'application/json'
},
body:
{ appsflyer_id: '<APPS_FLYER_ID>',
customer_user_id: '123456',
eventName: 'node_js',
eventValue: '{"node_js":"6" ,"af_content_id":"15854"}',
eventCurrency: 'USD',
ip: '1.0.0.0',
eventTime: '2018-07-09 4:17:00.000'
},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
/* using the RestSharp package, install using NuGet */
using System;
using RestSharp;
namespace CS
{
class Event
{
static void Main(string[] args)
{
var client = new RestClient("https://api2.appsflyer.com/inappevent/<APP_ID>");
var request = new RestRequest(Method.POST);
request.AddHeader("authentication", "<YOUR_DEV_KEY>");
request.AddHeader("Content-Type", "application/json");
var body = "{\"appsflyer_id\": \"<APPS_FLYER_ID>\"," +
"\"customer_user_id\": \"123456\"," +
"\"eventName\": \"af_purchase\"," +
"\"eventValue\": \"{\\\"af_revenue\\\":\\\"6\\\" ,\\\"af_content_id\\\":\\\"15854\\\"}\"," +
"\"eventCurrency\": \"USD\"," +
"\"eventTime\": \"2018-07-08 4:17:00.000\"
}";
request.AddParameter("undefined", body, ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
// handle response by reading response.StatusCode
Console.WriteLine(response.Content);
}
}
}
$purchase_event = array(
'appsflyer_id' => <APPS_FLYER_DEVICE_ID>,
'idfa' => <IDFA>,
'eventCurrency' => <PURCHASE_CURRENCY>,
'ip' => <DEVICE_ID_ADDRESS>,
'eventTime' => date("Y-m-d H:i:s.000",time())
);
$purchase_event['eventName'] = 'af_purchase';
$purchase_event['eventValue'] = json_encode(array('af_revenue' => <PURCHASE_REVENUE>,
'af_price' => <PURCHASE_PRICE>,
'af_order_id' => <PURCHASE_ORDER_ID>,
'af_currency' => <PURCHASE_CURRENCY>,
'af_content_type' => <PURCHASE_TYPE>,
'af_quantity' => <PURCHASE_QUANTITY>,
'af_content' => <PRODUCT_NAME>,
'af_content_id' => <PRODUCT_ID>),
JSON_UNESCAPED_UNICODE);
$data_string = json_encode($purchase_event);
$ch = curl_init('https://api2.appsflyer.com/inappevent/<YOUR_APP_ID>');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'authentication: <YOUR_APPS_FLYER_DEV_TOKEN>',
'Content-Length: ' .strlen($data_string))
);
$result = curl_exec($ch);
$curl = curl_init();
AppsFlyer Go script on Github
广告ID/设备ID的重要性
- 广告ID/设备ID是必须配置的参数,这是为了确保将数据回传到Facebook和Google Ads等SRN。如果您不发送该ID,我们是无法发送回传数据的。
- 如果不发送广告ID/设备ID而仅发送AppsFlyer ID,不会影响应用内事件的正确记录及归因。
填充参数
自然量与非自然量的区别
AppsFlyer处理S2S应用内事件时,会通过AppsFlyer ID来识别相关应用内事件之前的激活事件,从而填充归因字段。
也就是说,AppsFlyer会将某些数据与非自然的S2S应用内事件相关联,而这些数据与自然的S2S应用内事件是不相关的。
示例
如果对比非自然和自然的S2S应用内事件原始数据报告, 会发现非自然事件中包含自然事件所没有的数据。
这些数据包括媒体渠道、广告系列、触点类型、触达时间等。
这是因为自然事件来自于自然激活,而自然激活本来就没有广告系列、媒体渠道、触点类型和触达时间等数据。
AppsFlyer ID与客户用户ID(CUID)之间的映射
需要通过后端逻辑获取相关的值来填充参数。请按以下步骤获取AppsFlyer ID:
- AppsFlyer ID是必须配置的参数,用于归因事件。
- 该ID是在用户首次激活应用时生成的。
- 如果需要将CUID映射到AppsFlyer ID,请务必在应用中设置CUID。
请部署以下链路,以便将用户关联到事件:
- 在用户激活应用时设置客户用户ID(CUID)
- AppsFlyer原始数据报告中会包含CUID和AppsFlyer ID。您可以使用任意数据传输工具或AppsFlyer的Push API来获取该数据。
- 使用原始数据报告将CUID与AppsFlyer ID进行匹配。
- 您在应用(安卓/iOS)中接入AppsFlyer SDK后,SDK会在用户激活应用时生成AppsFlyer ID。
- 将AppsFlyer ID映射到内部系统中的客户用户ID(CUID)(后续会有重要作用)。
AppsFlyer ID与CUID之间形成映射后,就可以将用户匹配到他们完成的事件。然后,您可以获取其他值(事件值,事件币种,事件时间等),并发送S2S应用内事件。
请求示例
{
"appsflyer_id": "1415211453000-6513894",
"advertising_id": "38412345-8cf0-aa78-b23e-10b96e40000d",
"eventName": "af_purchase",
"eventValue":
"{
\"af_revenue\": \"6\",
\"af_currency\" \"USD"\",
\"af_content_type\": \"wallets\",
\"af_content_id\": \"15854\",
\"af_quantity\" :\"1\"
}",
"eventCurrency": "USD",
"ip": "1.2.3.4",
"eventTime": "2014-05-15 12:17:00.000"
}
本示例中,AppsFlyer收到一个S2S应用内事件。根据参数和对应的值可以判断,这是一个包含收入、内容类型等属性的购买事件。
提取AppsFlyer ID
发送负收入
您可以发送收入值为负的事件(如订单被取消)。af_revenue
参数可以通过负值来记录此信息。
如果使用af_quantity
,请根据您的系统逻辑来决定是否在其中填充负值。AppsFlyer是不使用af_quantity
的。
负收入示例
{
“ eventName”:“ cancel_purchase”,
“ eventValue”:
“ {
\"af_revenue\": \"-6\",
\"af_content_type\": \"wallets\",
\"af_content_id\": \"15854\",
\"af_quantity\" :\"1\"
} ”,
“ eventCurrency”:“ USD”,
}
疑难解答
事件未显示在面板上
- 端点(endpoint):请检查您所使用的节点(endpoint)是否正确。
- 请检查Payload中是否包含必需参数。详情请见此处。
- 请确保您用来触发事件的AppsFlyer ID是真实的appsflyer_id,并且已存入应用中。这里的ID不是文档中提供的测试ID,详情请见此处。
- 一个S2S请求中不能包含多个事件,每个事件都必须单独发送。
- 在数据总览面板中,日期范围中的日期是指应用激活日期(即LTV维度),而非事件发生日期。
- 确保您选择正确的日期范围。
- 确保面板日期范围对应于设备的激活日期(appsflyer_id),而不是事件的日期。
- 事件原始数据报告:日期范围中的日期是事件发生日期,而非应用激活日期。
事件不包含收入
如果发送的S2S事件中未记录收入:请确保JSON是字符串格式的。其中最重要的部分是事件值参数,请务必将其转化为下图示例中的格式。
"{\"af_revenue\":\"6\" ,\"af_content_type\":\"wallets\"}"
如果事件值未转化成字符串格式,则无法得到正确处理,导致记录不到收入。
收入值不能带有任何格式。该值可以包含一个小数点,但不能带有货币符号、代码或,
分隔符,数值前可以带有-
。
- 有效值示例:
123
,-123.45
,123.456
- 无效值示例:
1,234.56
,1,234
S2S事件中并非所有字段都要填充
原始数据的字段是在调用S2S事件API后根据发送的值填充的,有的字段是根据SDK侧的应用激活填充的。由AppsFlyer SDK上报的应用内事件也会出现类似情况,但不完全相同,具体差别在于S2S事件不填充以下字段:
- WIFI
- Operator(运营商)
- Language(语言)
- Device Type(设备型号)
- Device Category(设备类别)
- App Version(应用版本,您可以使用
app_version_name
参数填充该值) - App Name(应用名称)