At a glance: Send events from your servers to AppsFlyer to measure mobile events that occur outside the app.
Server-to-server events API for mobile
The AppsFlyer platform attributes and records mobile app events sent by the AppsFlyer SDK and by APIs. Use the S2S API to report events that take place outside the app; for example, a user renews their subscription using your web interface. S2S events, once recorded, are available across the platform, including dashboards, raw data, and analytics. For PBA web events see Web S2S for PBA.
AppsFlyer populates S2S events with:
- Values sent in the S2S message
- Some AppsFlyer install attribution values like, install time and media source.
API use instructions
Create your API call using the information contained in the sections that follow.
S2S API facts
API endpoint |
|
HTTP method | POST |
Accepted content type | application/json |
Authorization |
|
Allowlist servers to get response messages | |
JSON payload limitation |
JSON payload size: up to1KB |
Rate limitation |
POST limitation volume: 60,000 POSTs per minute. To increase this limit, contact your CSM. |
Coding instructions |
|
Encode URLs |
Encode (percent) code reserved characters (https://tools.ietf.org/html/rfc3986#section-2.1) before forming the method URL. |
TLS |
Use TLS 1.2 or above. Ciphers supported |
Payload parameters
- Payload parameters consist of one or more device identifiers (depending on the operating system).
- What if I can't send a device identifier?
- You may be unable to send the identifier for a reason out of your control, for example, because the user has limited ad tracking (LAT) or uses iOS 14, and did not give ATT consent. If IDFV is available send it.
- Not sending an advertising ID/device identifier can cause:
- Postback issues: The media source will receive the postback but without a device identifier; consequently the media source can't associate it with a user.
- Audiences segmentation and rule failure. Audiences rulesets require identifiers. Send a device ID or customer user ID according to the ID type your ruleset uses.
Operating system | Identifier name | Description |
---|---|---|
iOS
|
idfa |
Where available populate with the device IDFA Format: String Example: |
idfv |
Where available populate with the device IDFV. Format: String |
|
Android |
advertising_id |
Where available populate with the device GAID (advertising ID) Format: String Example: |
oaid |
Format: String Example: |
|
amazon_aid |
Format: String |
|
imei |
Format: String Example: |
Parameter Name | Mandatory | Description | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
appsflyer_id |
Yes |
A unique identifier, generated by AppsFlyer, when the app launches for the first time.
|
|||||||||
customer_user_id |
No |
Customer user ID, a unique user identifier set by the app owner.
|
|||||||||
att |
No |
iOS ATTrackingManager authorization status
Note! We recommend that you populate |
|||||||||
ip |
No |
The mobile device's IP address during the event occurrence.
|
|||||||||
Deprecated |
|
||||||||||
eventName |
Yes |
Specify the event name. Ensure that the event names are aligned with the marketer's requirements.
|
|||||||||
eventValue |
Yes |
If you send an event without a value then send:
|
|||||||||
app_version_name |
No |
Your app version or identifier.
|
|||||||||
app_store |
No |
Equivalent to AF_STORE in Android apps. The store from which the app was downloaded.
|
|||||||||
eventTime |
No |
The time the event occurred using UTC timezone.
Close of day:
Example
|
|||||||||
eventCurrency |
No |
Currency code using ISO 4217 3-character codes and BCN (Bitcoin)
|
|||||||||
bundleIdentifier |
No* |
A unique app identifier. In raw-data, the parameter populates Bundle ID. [Best practice] Always populate this parameter. Many ad networks require it for campaign optimization.
|
|||||||||
sharing_filter |
No |
The sharing filter blocks the sharing of S2S events via postbacks/API with integrated partners and other third-party integrations. Use the filter to fulfill regulatory requirements like GDPR and CCPA, to comply with user opt-out mechanisms, and for other business logic reasons. The sharing_filter has the following options:
For a list of partner ids, contact your CSM or AppsFlyer support. |
|||||||||
custom_dimension |
No |
Reserved for AppsFlyer future use |
|||||||||
app_type |
No |
For iOS apps. Permitted value: If the user event takes place in an app_clip, send the parameter. In all other cases don't send the parameter. |
|||||||||
custom_data |
No |
Send custom data to the AppsFlyer platform. Similar to sending data from the SDK using In raw data: populates Format: JSON, illustrated in the example that follows. Example:
|
|||||||||
* Required by many ad networks for optimization purposes |
Curl example
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",
"eventValue":
"{
\"af_revenue\": \"1006\",
\"af_content_type\": \"wallets\",
\"af_content_id\": \"15854\",
\"af_quantity\" :\"1\"
}"
}
'
Response codes
Response code | Message | How to Handle |
---|---|---|
200 | OK |
On receipt of a message, minimum data validation is performed. As such, you can get an OK response even though the event may not completely record in AppsFlyer. To debug events:
|
400 | Failed to Authenticate | Ensure that the authentication key is correct. |
400 | appsflyer_id is a mandatory field |
|
401 | Unauthorized | When the key provided in the authentication header is not the <dev_key> for this app. |
400 | Bad request |
When the request failed at least one of the validation criteria. |
400 | Payload is missing or failed to parse |
|
500 | Internal Server Error | Verify that the JSON is stringified() and formatted correctly. |
Testing
Consider:
- For testing, use the AppsFlyer ID of a non-organic install so that test event attributes in real-time. Organic events attribute with a delay of a few hours.
- Some event fields are populated using the install attribution event of the user. For example, install time and media source.
To attribute the test device as a non-organic install:
- Prepare a custom attribution link for testing S2S messages. Set the media source parameter on the link as to s2s_test and set advertising id (GAID, IDFA, etc) on the link as illustrated in the example attribution link snippets.
- Register the test device
- Uninstall the app from the test device.
- Send the link to the test device.
- Click on the link.
- Install and then launch the app.
- In the dashboard, check to see that the install is attributed.
- Fetch the AppsFlyer ID to use in your S2S message.
To send the S2S test message:
- Prepare an S2S message using the AppsFlyer ID allocated. See the following for code examples.
- Send the message.
- Do one of the following to see how the message is recorded in AppsFlyer:
- Download the in-app events raw data report. Allow up to 15 minutes after sending the event for it to show up in the raw data report.
- Push API (Consider using tools like Postman and Webhook site.)
- Check and view that:
- Values sent in the S2S message correctly populate the report. Pay special attention to the Event date, Event currency, Event revenue, and Event value fields.
- The attribution source and install time are populated by AppsFlyer.
- Values provided by the SDK itself like SDK version are not populated.
Example attribution link
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>
Example code for sending S2S messages
/* 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\"" +
"eventCurrency\": \"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 web 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>)
);
$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
Sending the advertising ID/device identifier is important
- The advertising ID/device identifier is mandatory to assure postbacks to SRNs like Facebook and Google Ads. If you are unable to send the ID, take into consideration that postbacks can't be sent.
- If you send only the AppsFlyer ID, in-app events will be recorded and attributed correctly.
Populating parameters
Difference between organic and non-organic
When AppsFlyer processes S2S in-app events, attribution fields are populated by using the AppsFlyer ID to identify the associated install event that preceded the in-app events.
What this means is that some data that AppsFlyer associates with non-organic S2S in-app events, is not associated with organic S2S in-app events.
Example
For example, if you compare raw data reports of non-organic and organic S2S in-app events, non-organic events contain data that organic in-app events do not.
Non-organic in-app events include data about the media source, campaign, attributed touch type, and attributed touch time.
Organic in-app events, on the other hand, follow an organic install. Organic installs don't have data related to campaign, media source, attributed touch type, and install time.
Mapping AppsFlyer ID with customer user ID (CUID)
Backend logic is required to obtain values to populate parameter. The following describes how you can get the AppsFlyer ID:
- The AppsFlyer ID is mandatory and is used to attribute events.
- It is generated when a user first installs the mobile app.
- So that you can map your CUID to the AppsFlyer ID, you must set the CUID in the app.
To make it easier for you to know which user performed which event, implement the flow that follows:
- Set customer user ID when the user installs the app.
- AppsFlyer raw data reports contain the CUID and AppsFlyer ID. Use one of the data delivery tools to get this or AppsFlyer Push API.
- Use the raw data reports to match the CUID with the AppsFlyer ID.
- The AppsFlyer ID is available in the SDK integrated into your app (Android/iOS).
- Map AppsFlyer ID to customer user ID in your internal systems (important for future use).
Once you map AppsFlyer ID with your CUID, you can match the user to events performed. You can then obtain the other values (event value, event currency, event time, etc.) and send the server to server in-app event.
Request body example
{
"appsflyer_id": "1415211453000-6513894",
"advertising_id": "38412345-8cf0-aa78-b23e-10b96e40000d",
"eventName": "af_purchase",
"eventValue":
"{
\"af_revenue\": \"6\",
\"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"
}
In this case, AppsFlyer receives an S2S in-app event which represents a purchase event with revenue, as well as additional properties such as content type, etc.
Fetching the AppsFlyer ID
appsflyer_id
is a mandatory parameter in server-to-server event messages. AppsFlyer uses this parameter to attribute the event to the original device and attributed media source. You can get the ID using one of the following methods:
- From the mobile device by calling the AppsFlyer SDK API: Android, iOS.
- From the AppsFlyer platform using one of the following: Pull API, Push API, Export raw data installation.
Tip
When testing S2S messages, if you're using raw data, look for the record with media source "s2s_test". This is your test device and its AppsFlyer Device ID is the ID you need.
Sending negative revenue
Events having a negative revenue value can be sent. For example, if a purchase was canceled. The parameter af_revenue
can have negative values to record this.
If you populate af_quantity
, you might want to populate it with a negative value depending on your system logic. AppsFlyer doesn't make use of af_quantity
.
Example with negative revenue
{
"eventName": "cancel_purchase",
"eventValue":
"{
\"af_revenue\": \"-6\",
\"af_content_type\": \"wallets\",
\"af_content_id\": \"15854\",
\"af_quantity\" :\"1\"
}",
"eventCurrency": "USD",
}
Sending events without event values
If you want to send events without event value, simply pass an empty string to event value: "eventValue":""
AppsFlyer is then able, according to the advertiser's configuration, to send such rich in-app events to media sources for advanced targeting, optimization, and audience creation purposes.
Troubleshooting
Events aren't displaying in the dashboard
- Endpoint: Verify that the endpoint used is correct.
- Verify that the payload contains the mandatory parameters. See here.
- Ensure that the AppsFlyer ID you are using to fire the event is a real appsflyer_id and is actually installed on the specific app. Not a test ID provided in the documentation. See here.
- S2S events don't support multi-events in one S2S request. Each event must be sent as a separate event.
- In the overview dashboard, the date range relates to the app install date (LTV) and not to the event date.
- Make sure you select the correct date range.
- Make sure that the dashboard date range corresponds to the install date of the device (appsflyer_id) and not the date of the event.
- Event raw data reports: the date range relates to the event date and not the install date.
Events don't contain revenue
If you send S2S events but their revenue is not recorded, ensure that the JSON that you send is stringified. The most important part is the event value parameter in the JSON. It must be stringified as shown in the example that follows.
"{\"af_revenue\":\"6\" ,\"af_content_type\":\"wallets\"}"
If it is not stringified, then the event value is not processed correctly and revenue is not recorded.
Revenue values must not be formatted in any way. They can contain a decimal comma. Don't include currency signs or codes nor ,
delimiters. The revenue can be prefixed by a -
- Example valid values are:
123
,-123.45
,123.456
- Example Illegal values:
1,234.56
,1,234
Not all fields are populated in S2S events
Raw data fields are populated using the value sent in the S2S call and some fields are populated using the install event. Similar behavior but not identical is observed for in-app events reported using the AppsFlyer SDK. There are some differences, specifically, the following fields are not populated for S2S events:
- WIFI
- Operator
- Language
- Device Type
- Device Category
- App Version: You can use
app_version_name
- App Name