In-app events
Overview
In-app events provide insights into how users interact with your app. The AppsFlyer SDK lets you log these interactions.
Anatomy of an event
In-app events consist of two parts:
- Event name: the unique event identifier. It's usually how marketers see the event in the dashboard.
- Event values: an object made up of key-value pairs called event parameters, which provide additional context about the event.
Event names and parameters can be predefined or custom.
TipQuickly define and generate in-app events code for all major platforms using the in-app event generator tool.
Predefined events and parameters are exposed as SDK constants (see Event constants below), prefixed with af_. Use the constants instead of raw strings: it avoids naming mistakes, and changes to the underlying names stay transparent to you.
Custom event names and parameters are user-defined and describe scenarios specific to your app.
AttentionTo avoid confusion with predefined events, don't prefix custom event names with
af_.
Valid custom event names: up to 100 characters, non-English characters supported.
Valid custom event parameters: up to 1000 characters (longer values may be truncated). Pricing and revenue values should use digits and decimals only (e.g. 5 or 5.2), up to 5 digits after the decimal point.
Before you begin
You must integrate the SDK.
Integrate In-App Events with our SDK wizard
Logging in-app events
The SDK lets you log user actions happening in the context of your app. These are commonly referred to as in-app events.
The logEvent method
logEvent methodThe logEvent method lets you log in-app events and send them to AppsFlyer for processing.
To access the logEvent method, import AppsFlyerLib:
import com.appsflyer.AppsFlyerLib;import com.appsflyer.AppsFlyerLibTo access predefined event constants, import AFInAppEventType and AFInAppEventParameterName:
import com.appsflyer.AFInAppEventType; // Predefined event names
import com.appsflyer.AFInAppEventParameterName; // Predefined parameter namesimport com.appsflyer.AFInAppEventType // Predefined event names
import com.appsflyer.AFInAppEventParameterName // Predefined parameter nameslogEvent takes 4 arguments:
void logEvent(Context context,
java.lang.String eventName,
java.util.Map<java.lang.String,java.lang.Object> eventValues,
AppsFlyerRequestListener listener)- The first argument (
context) is the Application/Activity Context - The second argument (
eventName) is the In-app event name - The third argument (
eventValues) is the event parametersMap - The fourth argument (
listener) is an optionalAppsFlyerRequestListener(useful for Handling event submission success/failure)
Example: Send "add to wishlist" event
For example, to log that a user added an item to their wishlist:
Map<String, Object> eventValues = new HashMap<String, Object>();
eventValues.put(AFInAppEventParameterName.PRICE, 1234.56);
eventValues.put(AFInAppEventParameterName.CONTENT_ID,"1234567");
AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
AFInAppEventType.ADD_TO_WISHLIST , eventValues);val eventValues = HashMap<String, Any>()
eventValues.put(AFInAppEventParameterName.PRICE, 1234.56)
eventValues.put(AFInAppEventParameterName.CONTENT_ID,"1234567")
AppsFlyerLib.getInstance().logEvent(getApplicationContext() ,
AFInAppEventType.ADD_TO_WISHLIST , eventValues)In the above logEvent invocation:
- The event name is
AFInAppEventType.ADD_TO_WISHLIST - The event value is a
Mapcontaining these event parameters:- AFInAppEventParameterName.PRICE: The price that's associated with the event
- AFInAppEventParameterName.CONTENT_ID: The identifier of the added item
Understanding event structure definitions
Before implementing an event, it helps to have a clear event structure definition. For example, a definition for an af_content_view event on an eCommerce app:
| Event name | Event parameters | Parameter values | Where/When |
|---|---|---|---|
af_content_view | af_price, af_content_type, af_content_id | af_price: item price. af_content_type: item category. af_content_id: item SKU. | When a user navigates to an item view. |
- The event name is the value you pass as the event name argument to
logEvent. - The event parameters list what to pass alongside it.
- Parameter values clarify what each parameter should contain.
- Where/When describes the trigger point in your app.
Implementing event structure definitions
Based on the example definition above, the event should be implemented as follows:
Map<String, Object> eventValues = new HashMap<String, Object>();
eventValues.put(AFInAppEventParameterName.PRICE, <ITEM_PRICE>);
eventValues.put(AFInAppEventParameterName.CONTENT_TYPE, <ITEM_TYPE>);
eventValues.put(AFInAppEventParameterName.CONTENT_ID, <ITEM_SKU>);
AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
AFInAppEventType.CONTENT_VIEW, eventValues);val eventValues = HashMap<String, Any>()
eventValues.put(AFInAppEventParameterName.PRICE, <ITEM_PRICE>)
eventValues.put(AFInAppEventParameterName.CONTENT_TYPE, <ITEM_TYPE>)
eventValues.put(AFInAppEventParameterName.CONTENT_ID, <ITEM_SKU>)
AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
AFInAppEventType.CONTENT_VIEW, eventValues)Handling event submission success and failure
You can provide logEvent with a AppsFlyerRequestListener object when recording in-app events. The handler allows you to define logic for two scenarios:
- An in-app event is recorded successfully
- An error occurred when recording the in-app event
AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
AFInAppEventType.PURCHASE,
eventValues,
new AppsFlyerRequestListener() {
@Override
public void onSuccess() {
Log.d(LOG_TAG, "Event sent successfully");
}
@Override
public void onError(int i, @NonNull String s) {
Log.d(LOG_TAG, "Event failed to be sent:\n" +
"Error code: " + i + "\n"
+ "Error description: " + s);
}
});AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
AFInAppEventType.PURCHASE,
eventValues,
object : AppsFlyerRequestListener {
override fun onSuccess() {
Log.d(LOG_TAG, "Event sent successfully")
}
override fun onError(errorCode: Int, errorDesc: String) {
Log.d(LOG_TAG, "Event failed to be sent:\n" +
"Error code: " + errorCode + "\n"
+ "Error description: " + errorDesc)
}
})In the event that an error occurs when recording the in-app event, an error code and string description are provided, as indicated in the table that follows.
| Error code | Description (NSError) |
|---|---|
10 | "Event timeout. Check 'minTimeBetweenSessions' param" |
11 | "Skipping event because 'isStopTracking' enabled" |
40 | Network error: Error description comes from Android |
41 | "No dev key" |
50 | "Status code failure" + actual response code from the server |
Recording offline events
The SDK can cache in-app events that occur when no internet connection is available:
- It sends events to AppsFlyer and waits for a response.
- If it doesn't receive a 200 response, the events are cached.
- Once the next 200 response arrives, cached events are re-sent, one network request per event (unbatched).
- The cache holds up to 40 events. Anything beyond that, until the next successful response, is discarded.
Logging events before calling start
startIf you initialized the SDK but didn't call start, the SDK will cache in-app events until start is invoked.
If there are multiple events in the cache, they are sent to the server one after another (unbatched, one network request per event).
Logging revenue
NoteFor events with revenue, including in-app purchases, subscriptions, and ad revenue events, AppsFlyer customers with an ROI360 subscription should avoid using the
AFInAppEventParameterName.REVENUE(af_revenue) parameter in their in-app events. Doing so can result in duplicate revenue being reported. Instead, they should utilize the purchase connector and the ad revenue SDK API.
You can send revenue with any in-app event. Use the AFInAppEventParameterName.REVENUE event parameter to include revenue in the in-app event. You can populate it with any numeric value, positive or negative.
The revenue value should not contain comma separators, currency signs, or text. A revenue event should be similar to 1234.56, for example.
Example: Purchase event with revenue
Map<String, Object> eventValues = new HashMap<String, Object>();
eventValues.put(AFInAppEventParameterName.CONTENT_ID, <ITEM_SKU>);
eventValues.put(AFInAppEventParameterName.CONTENT_TYPE, <ITEM_TYPE>);
eventValues.put(AFInAppEventParameterName.REVENUE, 200);
AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
AFInAppEventType.PURCHASE, eventValues);val eventValues = HashMap<String, Any>()
eventValues.put(AFInAppEventParameterName.CONTENT_ID, <ITEM_SKU>)
eventValues.put(AFInAppEventParameterName.CONTENT_TYPE, <ITEM_TYPE>)
eventValues.put(AFInAppEventParameterName.REVENUE, 200)
AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
AFInAppEventType.PURCHASE, eventValues)The purchase event above has $200 in revenue, appearing as revenue in the dashboard.
NoteDo not add currency symbols to the revenue value.
Configuring revenue currency
You can set the currency code for an event's revenue by using the af_currency predefined event parameter:
Map<String, Object> eventValues = new HashMap<String, Object>();
eventValues.put(AFInAppEventParameterName.CURRENCY, "USD");
eventValues.put(AFInAppEventParameterName.REVENUE, <TRANSACTION_REVENUE>);
AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
AFInAppEventType.PURCHASE, eventValues);val eventValues = HashMap<String, Any>()
eventValues.put(AFInAppEventParameterName.REVENUE, <TRANSACTION_REVENUE>)
eventValues.put(AFInAppEventParameterName.CURRENCY,"USD")
AppsFlyerLib.getInstance().logEvent(getApplicationContext() , AFInAppEventType.PURCHASE , eventValues)- The currency code should be a 3 character ISO 4217 code
- The default currency is USD
To learn about currency settings, display, and currency conversion, see our guide on revenue currency.
Logging net revenue
To report net revenue alongside gross revenue, add a custom parameter with the key af_net_revenue to the event values. The value represents net revenue according to your own business logic, for example revenue after store fees and/or taxes, gross margin, or revenue after service fees.
There is no predefined SDK constant for this parameter, so pass the string key directly. Follow the same formatting rules as af_revenue, and the same currency rules (currency via af_currency, default USD).
Map<String, Object> eventValues = new HashMap<String, Object>();
eventValues.put(AFInAppEventParameterName.REVENUE, 200); // gross
eventValues.put("af_net_revenue", 140); // net (custom key)
eventValues.put(AFInAppEventParameterName.CURRENCY, "USD");
AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
AFInAppEventType.PURCHASE, eventValues);val eventValues = HashMap<String, Any>()
eventValues.put(AFInAppEventParameterName.REVENUE, 200) // gross
eventValues.put("af_net_revenue", 140) // net (custom key)
eventValues.put(AFInAppEventParameterName.CURRENCY, "USD")
AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
AFInAppEventType.PURCHASE, eventValues)
NoteFor apps with ROI360 store revenue enabled,
af_net_revenueis populated automatically on the in-app purchase and subscription events recorded by ROI360 store revenue, so you do not need to send it for those events.
Logging negative revenue
There may be situations where you want to record negative revenue. For example, a user receives a refund or cancels a subscription.
To log negative revenue:
Map<String, Object> eventValues = new HashMap<String, Object>();
eventValues.put(AFInAppEventParameterName.REVENUE, -1234.56);
eventValues.put(AFInAppEventParameterName.CONTENT_ID,"1234567");
AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
"cancel_purchase",
eventValues);val eventValues = HashMap<String, Any>()
eventValues.put(AFInAppEventParameterName.REVENUE, -1234.56)
eventValues.put(AFInAppEventParameterName.CONTENT_ID,"1234567")
AppsFlyerLib.getInstance().logEvent(getApplicationContext(),
"cancel_purchase",
eventValues)
NoteNotice the following in the code above:
- The revenue value is preceded by a minus sign
- The event name is a custom event name called "cancel_purchase" - it helps you easily identify negative revenue events in the dashboard and raw data reports
Validating purchases
AppsFlyer provides server verification for in-app purchases. For more information see Validate and log purchase
Event constants
Predefined event names
To use the following constants, import com.appsflyer.AFInAppEventType:
import com.appsflyer.AFInAppEventType;import com.appsflyer.AFInAppEventTypePredefined event name constants follow a AFInAppEventType.EVENT_NAME naming convention. For example, AFInAppEventType.ADD_TO_CART
| Event name | Android constant name | |
|---|---|---|
"af_level_achieved" | AFInAppEventType.LEVEL_ACHIEVED | |
"af_add_payment_info" | AFInAppEventType.ADD_PAYMENT_INFO | |
"af_add_to_cart" | AFInAppEventType.ADD_TO_CART | |
"af_add_to_wishlist" | AFInAppEventType.ADD_TO_WISHLIST | |
"af_complete_registration" | AFInAppEventType.COMPLETE_REGISTRATION | |
"af_tutorial_completion" | AFInAppEventType.TUTORIAL_COMPLETION | |
"af_initiated_checkout" | AFInAppEventType.INITIATED_CHECKOUT | |
"af_purchase" | AFInAppEventType.PURCHASE | |
"af_rate" | AFInAppEventType.RATE | |
"af_search" | AFInAppEventType.SEARCH | |
"af_spent_credits" | AFInAppEventType.SPENT_CREDITS | |
"af_achievement_unlocked" | AFInAppEventType.ACHIEVEMENT_UNLOCKED | |
"af_content_view" | AFInAppEventType.CONTENT_VIEW | |
"af_list_view" | AFInAppEventType.LIST_VIEW | |
"af_travel_booking" | AFInAppEventType.TRAVEL_BOOKING | |
"af_share" | ||
"af_invite" | AFInAppEventType.INVITE | |
"af_login" | AFInAppEventType.LOGIN | |
"af_re_engage" | AFInAppEventType.RE_ENGAGE | |
"af_update" | AFInAppEventType.UPDATE | |
"af_location_coordinates" | AFInAppEventType.LOCATION_COORDINATES | |
"af_customer_segment" | AFInAppEventType.CUSTOMER_SEGMENT | |
"af_subscribe" | AFInAppEventType.SUBSCRIBE | |
"af_start_trial" | AFInAppEventType.START_TRIAL | |
"af_ad_click" | AFInAppEventType.AD_CLICK | |
"af_ad_view" | AFInAppEventType.AD_VIEW | |
"af_opened_from_push_notification" | AFInAppEventType.OPENED_FROM_PUSH_NOTIFICATION |
Predefined event parameters
To use the following constants, import AFInAppEventParameterName:
import com.appsflyer.AFInAppEventParameterName;import com.appsflyer.AFInAppEventParameterNamePredefined event parameter constants follow a AFInAppEventParameterName.PARAMETER_NAME naming convention. For example, AFInAppEventParameterName.CURRENCY
| Event parameter name | Android constant name | Type |
|---|---|---|
"af_content" | CONTENT | String[] |
"af_achievement_id" | ACHIEVEMENT_ID | String |
"af_level" | LEVEL | String |
"af_score" | SCORE | String |
"af_success" | SUCCESS | String |
"af_price" | PRICE | float |
"af_content_type" | CONTENT_TYPE | String |
"af_content_id" | CONTENT_ID | String |
"af_content_list" | CONTENT_LIST | String[] |
"af_currency" | CURRENCY | String |
"af_quantity" | QUANTITY | int |
"af_registration_method" | REGISTRATION_METHOD | String |
"af_payment_info_available" | PAYMENT_INFO_AVAILABLE | String |
"af_max_rating_value" | MAX_RATING_VALUE | String |
"af_rating_value" | RATING_VALUE | String |
"af_search_string" | SEARCH_STRING | String |
"af_date_a" | DATE_A | String |
"af_date_b" | DATE_B | String |
"af_destination_a" | DESTINATION_A | String |
"af_destination_b" | DESTINATION_B | String |
"af_description" | DESCRIPTION | String |
"af_class" | CLASS | String |
"af_event_start" | EVENT_START | String |
"af_event_end" | EVENT_END | String |
"af_lat" | LAT | String |
"af_long" | LONG | String |
"af_customer_user_id" | CUSTOMER_USER_ID | String |
"af_validated" | VALIDATED | boolean |
"af_revenue" | REVENUE | float |
"af_projected_revenue" | PROJECTED_REVENUE | float |
"af_receipt_id" | RECEIPT_ID | String |
"af_tutorial_id" | TUTORIAL_ID | String |
"af_virtual_currency_name" | VIRTUAL_CURRENCY_NAME | String |
"af_deep_link" | DEEP_LINK | String |
"af_old_version" | OLD_VERSION | String |
"af_new_version" | NEW_VERSION | String |
"af_review_text" | REVIEW_TEXT | String |
"af_coupon_code" | COUPON_CODE | String |
"af_order_id" | ORDER_ID | String |
"af_param_1" | PARAM_1 | String |
"af_param_2" | PARAM_2 | String |
"af_param_3" | PARAM_3 | String |
"af_param_4" | PARAM_4 | String |
"af_param_5" | PARAM_5 | String |
"af_param_6" | PARAM_6 | String |
"af_param_7" | PARAM_7 | String |
"af_param_8" | PARAM_8 | String |
"af_param_9" | PARAM_9 | String |
"af_param_10" | PARAM_10 | String |
"af_departing_departure_date" | DEPARTING_DEPARTURE_DATE | String |
"af_returning_departure_date" | RETURNING_DEPARTURE_DATE | String |
"af_destination_list" | DESTINATION_LIST | String[] |
"af_city" | CITY | String |
"af_region" | REGION | String |
"af_country" | COUNTRY | String |
"af_departing_arrival_date" | DEPARTING_ARRIVAL_DATE | String |
"af_returning_arrival_date" | RETURNING_ARRIVAL_DATE | String |
"af_suggested_destinations" | SUGGESTED_DESTINATIONS | String[] |
"af_travel_start" | TRAVEL_START | String |
"af_travel_end" | TRAVEL_END | String |
"af_num_adults" | NUM_ADULTS | String |
"af_num_children" | NUM_CHILDREN | String |
"af_num_infants" | NUM_INFANTS | String |
"af_suggested_hotels" | SUGGESTED_HOTELS | String[] |
"af_user_score" | USER_SCORE | String |
"af_hotel_score" | HOTEL_SCORE | String |
"af_purchase_currency" | PURCHASE_CURRENCY | String |
"af_preferred_neighborhoods" | PREFERRED_NEIGHBORHOODS | String[] |
"af_preferred_num_stops" | PREFERRED_NUM_STOPS | String |
"af_adrev_ad_type" | AD_REVENUE_AD_TYPE | String |
"af_adrev_network_name" | AD_REVENUE_NETWORK_NAME | String |
"af_adrev_placement_id" | AD_REVENUE_PLACEMENT_ID | String |
"af_adrev_ad_size" | AD_REVENUE_AD_SIZE | String |
"af_adrev_mediated_network_name" | AD_REVENUE_MEDIATED_NETWORK_NAME | String |
"af_preferred_price_range" | PREFERRED_PRICE_RANGE | String, int tuple formatted as (min,max) |
"af_preferred_star_ratings" | PREFERRED_STAR_RATINGS | String, int tuple formatted as (min,max) |
Updated 12 days ago