Server-to-server events API for mobile (S2S-mobile)

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

For iOS apps, starting iOS 14, you must send the os (operating system) parameter. 

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

  • app_id: The app identifier used in the AppsFlyer dashboard. Insert it precisely as it appears on the dashboard.
  • iOS apps: Ensure to prefix with id. Failure to do so results in a valid 200 OK return code; without recording the event.
  • Windows: Get the app ID from the Microsoft App Store.
  • Example Android:
HTTP method POST
Accepted content type application/json

--header 'authentication: dev_key'

  • The dev_key is the authentication token in the header. 
  • To get the dev key, in AppsFlyer go to, App Settings > Dev Key
Allowlist servers to get response messages

 If your servers are located behind a firewall, you must allowlist the domain so that you 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 ( before forming the method URL. 


Use TLS 1.2 or above. Ciphers supported

S2S API facts

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. It’s always best practice to send a device ID or customer user ID according to the ID type your ruleset uses, for every S2S event. However, if AppsFlyer identifiers have been sent through other means (SDK or past S2S event), new in-app events will be matched to the correct users even if no identifiers are sent with the events.       
Operating system Identifier name Description





Where available populate with the device IDFA

Format: String

Example: "idfa": "9876F1SS-2983-3855-27RR-2R626772VFNB"


Where available populate with the device IDFV.

Format: String
Example: "idfv": "95C9BD22-4A4C-41C8-9548-ED07C5C8C145"




Where available populate with the device GAID (advertising ID)

Format: String

Example: "advertising_id": "9c9a82fb-d5de-4cd1-90c3-527441c11828"


Format: String

Example: "oaid": "1fe9a970-efbb-29e0-0bdd-f5dbbf751ab5"


Format: String


Format: String

Example: "imei": "AA-BBBBBB-CCCCCC-D"

Device identifiers
Parameter Mandatory Description



A unique identifier, generated by AppsFlyer, when the app launches for the first time. 

  • Format: String
  • Example: "appsflyer_id": "1415211453000-6513894"



Customer user ID, a unique user identifier set by the app owner.

  • Format: String. 
  • Example: "customer_user_id": "my_customer_number1234" 



iOS ATTrackingManager authorization status

  • If the device OS version is iOS 14 or later, populate attwith ATTrackingManager.
    • Format: Single-digit integer. Don't enclose the value in ""
    • Example: 1
  • The iOS values for ATTrackingManager are:
    • 0: Not determined
    • 1: Restricted
    • 2: Denied
    • 3: Authorize


We recommend that you populate att with the ATTrackingManager value as it impacts which identifiers are shared with partners in accordance with your Aggregated Advanced Privacy settings. 




The mobile device's IP address during the event occurrence.

  • If sent, the IP address is used to populate geo fields.
  • If not sent, AppsFlyer populates the geo fields using the values from the install attribution event. The IP address isn't populated.
  • Format: String containing IPV4 or IPV6 address
  • Example: "ip": "
af_events_api Deprecated
  • Deprecated starting July 6, 2020.
  • There is no need for this parameter, you can stop sending it. Doing so does not impact attribution in any way. 



Specify the event name. Ensure that the event names are aligned with the marketer's requirements.

  • Format: String
  • Example: "eventName": "af_purchase"



If you send an event without a value then send: "eventValue":""

  • Event values must be sent without additional symbols or formatting. 
  • For af_revenue a decimal point can be used. Negative values should be preceded by a -
  • Format: Strings in a JSON
  • Example: "eventValue": "{ \"af_revenue\": \"6\", \"af_currency\": \"USD\",\"af_content_type\": \"wallets\", \"af_content_id\": \"15854\", \"your_custom_attribute\" :\"456.123\" }"
  • Explicit TikTok For Business instructions.



Your app version or identifier.

  • Format: String
  • Example: "app_version_name": "my_app_version"



Equivalent to AF_STORE in Android apps. The store from which the app was downloaded. 

  • Raw data field: Install App Store
  • Format: String
  • Example: my_android_store_is_best



The time the event occurred using UTC timezone.

  • Default: If noeventTimeis sent, the time is set to the HTTP message arrival time.
  • Format: string yyyy-mm-dd hh:mm:ss.sss the time needs to be in UTC timezone.
  • Example: "eventTime":"2019-05-15 12:17:01.123" means 12:17:01 UTC. 

Close of day:

  • If you implement eventTime the event must reach AppsFlyer by no later 02:00 of the following day. Meaning, if the event took place on Monday at 17:00 UTC, it must reach AppsFlyer servers by no later than Tuesday 02:00 UTC. 
  • Events received after close-of-day are stamped with the actual arrival time.
  • Events having a future time, meaning after the arrival time, are stamped with the arrival time. 


  • Times are in UTC
  • An event is sent with eventTime = Monday 21:00.
Time of arrival Time recorded by AppsFlyer Remark
Tuesday 01:00 Monday 21:00 Arrived before close-of-business.
Wednesday 09:00  Wednesday 09:00 Arrived after close-of-business. Time is set to arrival time. 



Currency code using ISO 4217 3-character codes and BCN (Bitcoin)

  • Default: USD
  • Example: "eventCurrency": "ZAR"



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. 

  • Format: String
  • Example: "bundleIdentifier": "com.myapp"



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:

  • all: All partners are blocked. Don't share the event with anyone. Example: "sharing_filter": "all"
  • List of partner ids in an array. Format ["partnerid_1", "partnerid_2", "partnerid_n"] Example: "sharing_filter": ["googleadwords_int", "adcolony_int"]

For a list of partner ids, contact your CSM or AppsFlyer support. 



Reserved for AppsFlyer future use



For iOS apps.

Permitted value: app_clip

If the user event takes place in an app_clip, send the parameter. In all other cases don't send the parameter.



Send custom data to the AppsFlyer platform. Similar to sending data from the SDK using setAdditionalData 

In raw data: populates custom_data field.

Format: JSON, illustrated in the example that follows. 


"custom_data" : {"key_group" : { 
"key_a1" : "value a1", 
"key_a2" : "value a2" 
"key_b" : "value b"



The device operating system version. Regard this parameter as mandatory for all platforms.

Format: String

Example: "os":"14.5.1"


Starting July 1, 2021, for iOS apps,  if you don't send this parameter we regard the data as having come from a device running iOS 14.5.



Use this flag to indicate if the user opted out of sharing their advertiser ID. 

Use this field for devices running Android (all versions), or iOS versions before 14. Note! For iOS 14.5+, use the att parameter

Populate this field as follows: 

  • "true", if the user agreed to share the advertiser ID. Example: "aie":"true"
  • "false", if the user enabled Limited Ad Tracking (LAT). Example: "aie":"false"

* Required by many ad networks for optimization purposes

** To enable correct data processing you must send this parameter. For backward compatibility reasons, we don't enforce this, so it isn't marked as obligatory.

Curl example

curl --location --request POST '<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": "",
	"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\" }" } '

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:

  • Using the CURL example in this article"
    • Update the app id and dev key details.
    • Change the appsflyer_id, and eventTime parameters. Use the current time and an appsflyer_id recently attributed as non-organic.
  • Send the call.
    • It should return with a 200 OK message.
    • Check to see if the event recorded correctly including the revenue
    • Make additional changes as needed and test again.
400 Failed to Authenticate Ensure that the authentication key is correct.
400  appsflyer_id is a mandatory field
  • Make sure you pass the correct AppsFlyer ID in the JSON. 
  • JSON is stringified() and formatted correctly.
401 Unauthorized When the key provided in the authentication header is not the <dev_key> for this app.
403 Forbidden When the app ID in the URL is incorrect.
400 Bad request

When the request failed at least one of the validation criteria.

400  Payload is missing or failed to parse
  • Make sure that the JSON is stringified and formatted correctly.
  • If more than one event is included in the JSON payload. Make sure to send one event per request.
500  Internal Server Error Verify that the JSON is stringified() and formatted correctly.



  • 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:

  1. 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. 
  2. Register the test device
  3. Uninstall the app from the test device.
  4. Send the link to the test device.
  5. Click on the link.
  6. Install and then launch the app.
  7. In the dashboard, check to see that the install is attributed.
  8. Fetch the AppsFlyer ID to use in your S2S message.

To send the S2S test message:

  1. Prepare an S2S message using the AppsFlyer ID allocated. See the following for code examples.
  2. Send the message.
  3. Do one of the following to see how the message is recorded in AppsFlyer:
  4. 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



Example code for sending S2S messages

JavaPythonNode JSC#PHPGo
/* using the okhttp package 
install from maven */
import okhttp3.*;

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()
        .addHeader("Content-Type", "application/json")
        .addHeader("authentication", "<YOUR_DEV_KEY>")

    try {
      Response response = client.newCall(request).execute();
    } catch (IOException e) {

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.


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",
		\"af_revenue\": \"6\",
\"af_currency\" \"USD"\", \"af_content_type\": \"wallets\", \"af_content_id\": \"15854\", \"af_quantity\" :\"1\" }", "eventCurrency": "USD", "ip": "", "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:


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",
		\"af_revenue\": \"-6\",
		\"af_content_type\": \"wallets\",
		\"af_content_id\": \"15854\",
		\"af_quantity\" :\"1\"
	"eventCurrency": "USD",


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.
    • Events can be sent with asynchronous method to ease response time.
  • 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 point. Don't include currency signs or codes nor , delimiters. The revenue can be prefixed by a -

  • Example valid values are: 123, -123.45123.456 
  • Example Illegal values: 1,234.561,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