Documentation Index Fetch the complete documentation index at: https://docs.trygravity.ai/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Track app installs back to the Gravity ad that drove them. Two integration paths:
Server-to-Server Your backend calls the Gravity Conversions API when a user installs your app. Full control over data and timing.
MMP Postback Your MMP (AppsFlyer, Adjust, etc.) fires postbacks to Gravity automatically. No code on your side.
Both paths write to the same conversion pipeline. Click-through and view-through attribution are supported.
How attribution works
User sees or clicks a Gravity ad — Gravity captures a unique click identifier
User lands on your website or App Store page
User installs and opens your app
The install is reported to Gravity (via your backend or MMP) with the click identifier
Gravity attributes the install back to the originating campaign and ad
Gravity attribution does not depend on IDFA, so this works with iOS 14.5+ App Tracking Transparency restrictions.
Option 1: Server-to-Server
Best when you control the app backend and can capture the click identifier from the ad click URL.
Step 1: Capture the click identifier on your landing page
When a user arrives from a Gravity ad, the URL includes a click_id parameter:
https://yourapp.com/download?click_id=abc-123
Capture and persist it server-side:
const params = new URLSearchParams ( window . location . search );
const clickId = params . get ( 'click_id' );
// Store server-side (preferred) or in a first-party cookie
fetch ( '/api/store-attribution' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ click_id: clickId }),
});
Step 2: Pass the click identifier through your deep link
If you use a deep link provider (Branch, Firebase Dynamic Links, or a custom universal link), pass the click_id as a custom parameter so it survives the Web → App Store → App flow.
Step 3: Report the install
When the user opens your app for the first time, fire a conversion from your backend.
Python
Node.js
Swift
Kotlin
import requests
import hashlib
import time
API_KEY = "your-gravity-api-key"
GATEWAY_URL = "https://conversions.trygravity.ai/gateway/events"
def report_app_install ( user , click_id ):
email_hash = hashlib.sha256(
user[ "email" ].strip().lower().encode()
).hexdigest() if user.get( "email" ) else None
payload = {
"data" : [{
"event_name" : "AppInstall" ,
"event_time" : int (time.time()),
"event_id" : f "install- { user[ 'id' ] } - { int (time.time()) } " ,
"action_source" : "app" ,
"user_data" : {
"click_id" : click_id,
"em" : [email_hash] if email_hash else None ,
"external_id" : [ str (user[ "id" ])],
},
"custom_data" : {
"value" : 0 ,
"currency" : "USD" ,
}
}]
}
return requests.post(
GATEWAY_URL ,
json = payload,
headers = { "Authorization" : f "Bearer { API_KEY } " },
timeout = 10 ,
).json()
import crypto from 'crypto' ;
const API_KEY = 'your-gravity-api-key' ;
const GATEWAY_URL = 'https://conversions.trygravity.ai/gateway/events' ;
async function reportAppInstall (
user : { id : string ; email ?: string },
clickId : string ,
) {
const hashPii = ( val : string ) =>
crypto . createHash ( 'sha256' )
. update ( val . trim (). toLowerCase ())
. digest ( 'hex' );
const payload = {
data: [{
event_name: 'AppInstall' ,
event_time: Math . floor ( Date . now () / 1000 ),
event_id: `install- ${ user . id } - ${ Date . now () } ` ,
action_source: 'app' ,
user_data: {
click_id: clickId ,
em: user . email ? [ hashPii ( user . email )] : undefined ,
external_id: [ user . id ],
},
custom_data: { value: 0 , currency: 'USD' },
}],
};
const resp = await fetch ( GATEWAY_URL , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ API_KEY } ` ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ( payload ),
});
return resp . json ();
}
import Foundation
import CryptoKit
struct GravityInstallReporter {
static let apiKey = "your-gravity-api-key"
static let gatewayURL = URL (
string : "https://conversions.trygravity.ai/gateway/events"
) !
static func reportInstall (
userId : String ,
email : String ? ,
clickId : String
) {
var userData: [ String : Any ] = [
"click_id" : clickId,
"external_id" : [userId],
]
if let email {
let normalized = email
. lowercased ()
. trimmingCharacters ( in : . whitespaces )
let hash = SHA256. hash ( data : Data (normalized. utf8 ))
userData[ "em" ] = [hash. map {
String ( format : "%02x" , $0 )
}. joined ()]
}
let event: [ String : Any ] = [
"event_name" : "AppInstall" ,
"event_time" : Int ( Date (). timeIntervalSince1970 ),
"event_id" : "install- \( userId ) - \( Int ( Date (). timeIntervalSince1970 ) ) " ,
"action_source" : "app" ,
"user_data" : userData,
"custom_data" : [ "value" : 0 , "currency" : "USD" ],
]
let payload: [ String : Any ] = [ "data" : [event]]
var request = URLRequest ( url : gatewayURL)
request. httpMethod = "POST"
request. setValue (
"Bearer \( apiKey ) " ,
forHTTPHeaderField : "Authorization"
)
request. setValue (
"application/json" ,
forHTTPHeaderField : "Content-Type"
)
request. httpBody = try ? JSONSerialization. data (
withJSONObject : payload
)
URLSession. shared . dataTask ( with : request) { _ , _ , _ in }
. resume ()
}
}
import java.net.HttpURLConnection
import java.net.URL
import java.security.MessageDigest
object GravityInstallReporter {
private const val API_KEY = "your-gravity-api-key"
private const val GATEWAY_URL =
"https://conversions.trygravity.ai/gateway/events"
fun reportInstall (
userId: String ,
email: String ? = null ,
clickId: String ,
) {
Thread {
val emailHash = email?. let {
sha256 (it. trim (). lowercase ())
}
val userData = mutableMapOf < String , Any >(
"click_id" to clickId,
"external_id" to listOf (userId),
)
emailHash?. let { userData[ "em" ] = listOf (it) }
val event = mapOf (
"event_name" to "AppInstall" ,
"event_time" to (System. currentTimeMillis () / 1000 ),
"event_id" to "install- $userId - ${ System. currentTimeMillis () } " ,
"action_source" to "app" ,
"user_data" to userData,
"custom_data" to mapOf (
"value" to 0 , "currency" to "USD"
),
)
val payload = """{"data":[ ${
org.json. JSONObject (event)
} ]}"""
val conn = URL (GATEWAY_URL). openConnection ()
as HttpURLConnection
conn.requestMethod = "POST"
conn. setRequestProperty (
"Authorization" , "Bearer $API_KEY "
)
conn. setRequestProperty (
"Content-Type" , "application/json"
)
conn.doOutput = true
conn.outputStream. write (payload. toByteArray ())
conn.responseCode
conn. disconnect ()
}. start ()
}
private fun sha256 (input: String ): String {
val bytes = MessageDigest. getInstance ( "SHA-256" )
. digest (input. toByteArray ())
return bytes. joinToString ( "" ) { "%02x" . format (it) }
}
}
Post-install events
Track in-app purchases and other downstream events with the same pattern:
payload = {
"data" : [{
"event_name" : "InAppPurchase" ,
"event_time" : int (time.time()),
"event_id" : f "purchase- { order_id } " ,
"action_source" : "app" ,
"user_data" : {
"click_id" : stored_click_id,
"em" : [email_hash],
"external_id" : [user_id],
},
"custom_data" : {
"value" : 29.99 ,
"currency" : "USD" ,
"order_id" : order_id,
"content_name" : "Premium Subscription" ,
}
}]
}
App event types
Event Name Internal Type When to fire AppInstallapp_installFirst app open after install AppOpenapp_openSubsequent app opens (re-engagement) InAppPurchasein_app_purchaseIn-app purchase completed Any custom string Lowercased Custom app events
View-through attribution (no click)
If the user installs without clicking an ad (e.g., they saw the ad but went to the App Store directly), send the event without a click_id. Include the user’s email hash — Gravity will attempt to attribute the install to a recent ad impression.
payload = {
"data" : [{
"event_name" : "AppInstall" ,
"event_time" : int (time.time()),
"event_id" : f "install- { user_id } " ,
"action_source" : "app" ,
"user_data" : {
"em" : [email_hash],
"client_ip_address" : user_ip,
"external_id" : [user_id],
},
"custom_data" : { "value" : 0 , "currency" : "USD" }
}]
}
Option 2: MMP Postback
Best when you already use an MMP. No code changes on your side — configure Gravity as a partner in your MMP dashboard.
Supported MMPs
MMP Postback URL AppsFlyer https://conversions.trygravity.ai/mmp/postback/appsflyerAdjust https://conversions.trygravity.ai/mmp/postback/adjustKochava https://conversions.trygravity.ai/mmp/postback/kochavaBranch https://conversions.trygravity.ai/mmp/postback/branchSingular https://conversions.trygravity.ai/mmp/postback/singularOther https://conversions.trygravity.ai/mmp/postback?provider=<name>
Setup
Configure your click URL
In your MMP’s partner configuration for Gravity, ensure that the click identifier from Gravity’s ad URL is captured by the MMP as a custom parameter. Most MMPs call this clickid, click_id, or label.
Add the postback URL
In your MMP dashboard, add Gravity as a postback partner. Use the URL template for your MMP (see examples below).
Test the integration
Send a test postback to verify: curl "https://conversions.trygravity.ai/mmp/postback/appsflyer \
?clickid=test-click-123 \
&event_name=install \
&event_time=$( date +%s) \
&ip=203.0.113.50 \
&app_id=com.example.app \
&api_key=YOUR_API_KEY"
Postback URL templates
AppsFlyer
Adjust
Kochava
Branch
Singular
https://conversions.trygravity.ai/mmp/postback/appsflyer
?clickid={clickid}
&event_name={event_name}
&event_time={event_time}
&idfa={idfa}
&ip={ip}
&revenue={event_revenue}
¤cy={event_revenue_currency}
&app_id={app_id}
&country_code={country_code}
&campaign={campaign_name}
&api_key=YOUR_GRAVITY_API_KEY
https://conversions.trygravity.ai/mmp/postback/adjust
?click_id={click_id}
&activity_kind={activity_kind}
&created_at={created_at}
&idfa={idfa}
&ip_address={ip_address}
&revenue={revenue}
¤cy={currency}
&app_id={app_id}
&country={country}
&campaign_name={campaign_name}
&api_key=YOUR_GRAVITY_API_KEY
https://conversions.trygravity.ai/mmp/postback/kochava
?click_id={click_id}
&event_name={event_name}
&event_timestamp={event_timestamp}
&idfa={idfa}
&device_ip={device_ip}
&revenue={revenue}
¤cy={currency}
&app_id={app_id}
&country_code={country_code}
&campaign_name={campaign_name}
&api_key=YOUR_GRAVITY_API_KEY
https://conversions.trygravity.ai/mmp/postback/branch
?click_id={click_id}
&event_name={event_name}
×tamp={timestamp}
&ip={ip}
&revenue={revenue}
&revenue_currency={revenue_currency}
&api_key=YOUR_GRAVITY_API_KEY
https://conversions.trygravity.ai/mmp/postback/singular
?click_id={click_id}
&event_name={event_name}
×tamp={timestamp}
&idfa={idfa}
&ip={ip}
&revenue={revenue}
¤cy={currency}
&campaign_name={campaign_name}
&api_key=YOUR_GRAVITY_API_KEY
MMP event mapping
The postback receiver normalizes common MMP event names:
MMP Event Gravity Event Type install, attributed_install, first_openapp_installsession, re_engagement, app_openapp_openpurchase, in_app_purchase, af_purchasein_app_purchaseregistration, signupcomplete_registrationCustom events Passed through as-is (lowercased)
Both paths return the same structure:
{
"status" : "ok" ,
"conversion_id" : "uuid" ,
"attributed" : true ,
"event_type" : "app_install" ,
"provider" : "appsflyer"
}
Status Meaning okProcessed and stored duplicateAlready seen (deduplicated) test_okTest mode — validated but not stored errorProcessing failed
FAQ
Do I need IDFA for attribution?
No. Gravity does not depend on IDFA for attribution. This works with iOS 14.5+ App Tracking Transparency restrictions. If the user consented to tracking and you have IDFA, you can include it for additional signal, but it’s not required.
What if the user doesn't click the ad?
Send the conversion without click_id but include the user’s email hash (em). Gravity will attempt to attribute the install to a recent ad impression.
Can I track post-install events?
Yes. Use InAppPurchase (or any custom event name) with action_source: "app". Include custom_data.value for revenue attribution. Use the same click_id from the original install to maintain the attribution chain.
S2S vs MMP — which should I use?
S2S gives you full control — you call the API from your backend with all available data. MMP is easier if you already use one — configure a URL template and the MMP handles the rest. Both feed into the same conversion pipeline.
Next
Pixel & web conversions Track web conversions (purchases, signups, etc.).
Analytics Reports, attribution windows, and ROAS.