To optimize prospecting campaigns (which acquire new customers), AppLovin highly recommends that you provide AppLovin with order history data. This allows AppLovin to build an accurate model of your existing customers so it can find new, high-value users.
If you are a Shopify user who installed the AppLovin Shopify app and connected it to your Ads Manager account, you’re ready!
Otherwise, follow the requirements below to upload this data in CSV form.
Providing customer data to AppLovin is subject to your own privacy compliance requirements, including any necessary notices and consents.
transaction_id.
AppLovin ignores any data row with a transaction_id that already exists in its system.
If rows with duplicate transaction_ids exist in a single file, AppLovin keeps the one with the earliest event_timestamp and discards the rest.Your CSV file must include all the required headers listed below, though certain fields are optional to populate.
The column order does not matter, but the header names must match exactly.
Event window: AppLovin strongly encourages you to upload all available order history.
| Field | Value required? | Type | Description | Value |
|---|---|---|---|---|
country_code | Yes | String | An ISO 3166 country code | US |
currency | Yes | String | The currency of the transaction in ISO 4217 format (3-letter code) | USD |
email | Yes | String | The customer’s email address. Provide the plaintext email; AppLovin will hash it on its side. | customer@example.com |
event_name | Yes | String | Must be purchase | purchase |
event_timestamp | Yes | String | The time the purchase occurred
|
|
transaction_id | Yes | String | A unique identifier for the order (e.g., order ID, checkout ID). This is crucial for deduplication. | txn_12345 |
user_id | Yes | String | Your internal customer ID. | user_abc123 |
value | Yes | Double | The total value of the transaction. Must be a number greater than or equal to 0. Do not include currency symbols. | 99.99 |
zip | No | String | The customer’s zip code. This must be the billing zip code of the transaction. For U.S. zip-codes, only include the first five digits. | 12345 |
idfv | No | String | The user’s identifier for vendors. | f325g3gb-12fc-352f-c6c3-dz52f0f690d8 |
ifa | No | String | The user’s identifier for advertisers. This is either IDFA or GAID. | 918f1d4f-d195-4a8b-af47-44683fe11db9 |
phone | No | String | The customer’s phone number. Must include the country code (e.g., +1). Provide the plaintext number; AppLovin will hash it on its side. | +14155551234 |
AppLovin hashes plaintext email addresses and phone numbers that you provide.
emailFor gmail.com and googlemail.com only, first remove all periods before
the @, and remove the first plus sign and everything after it. Leave the
domain unchanged. If nothing remains before the @, produce an empty value.
phone1 for United States phone numbers).
Then hash via SHA256.
For example (for email addresses):
| Input | Normalized | SHA256 |
|---|---|---|
Alice@Example.COM | alice@example.com | ff8d9819fc0e12bf0d24892e45987e249a28dce836a85cad60e28eaaa8c6d976 |
J..A.NE+work.more+extra@GMAIL.COM | jane@gmail.com | 988b074286b20c503e3015c2076533f3bf4ce5ca6f8a507ab52c2e0f98d620b7 |
Jane.Doe+Work@GoogleMail.com | janedoe@googlemail.com | 338abf9ef1c8793cadc7bcf51ed595338eb727ed9e06ce3d91d566d60b975937 |
Reference implementations follow. Each one prints the hash for the second example above; run it and confirm you get the same value.
#include <algorithm>
#include <cctype>
#include <iostream>
#include <regex>
#include <string>
#include <openssl/sha.h>
std::string normalizeAndHash(std::string email)
{
email.erase(0, email.find_first_not_of(" \t\n\r\f\v"));
email.erase(email.find_last_not_of(" \t\n\r\f\v") + 1);
std::transform(email.begin(), email.end(), email.begin(),
[](unsigned char c) { return std::tolower(c); });
static const std::regex valid("^[!-?A-~]+@[!-?A-~]+$");
if (!std::regex_match(email, valid))
return "";
const auto at = email.find('@');
std::string local = email.substr(0, at);
const std::string domain = email.substr(at + 1);
if (domain == "gmail.com" || domain == "googlemail.com")
{
local = local.substr(0, local.find('+'));
local.erase(std::remove(local.begin(), local.end(), '.'), local.end());
if (local.empty())
return "";
}
const std::string normalized = local + "@" + domain;
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const unsigned char*>(normalized.data()),
normalized.size(), hash);
static constexpr char digits[] = "0123456789abcdef";
std::string out;
for (const unsigned char value : hash)
{
out += digits[value >> 4];
out += digits[value & 15];
}
return out;
}
int main()
{
std::cout << normalizeAndHash(" J..A.NE+work.more+extra@GMAIL.COM ") << "\n";
}
using System;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
class Program
{
static readonly Regex Valid = new Regex("^[!-?A-~]+@[!-?A-~]+$");
static string NormalizeAndHash(string email)
{
string trimmed = email.Trim(' ', '\t', '\n', '\r', '\f', '\v').ToLowerInvariant();
if (!Valid.IsMatch(trimmed))
return "";
int at = trimmed.IndexOf('@');
string local = trimmed.Substring(0, at);
string domain = trimmed.Substring(at + 1);
if (domain == "gmail.com" || domain == "googlemail.com")
local = local.Split('+')[0].Replace(".", "");
if (local.Length == 0)
return "";
using (SHA256 sha256 = SHA256.Create())
{
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(local + "@" + domain));
StringBuilder output = new StringBuilder(hash.Length * 2);
foreach (byte value in hash)
output.Append(value.ToString("x2"));
return output.ToString();
}
}
static void Main()
{
Console.WriteLine(NormalizeAndHash(" J..A.NE+work.more+extra@GMAIL.COM "));
}
}
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.regex.Pattern;
public final class EmailHash
{
private static final Pattern VALID = Pattern.compile( "^[!-?A-~]+@[!-?A-~]+$" );
static String normalizeAndHash( final String email ) throws NoSuchAlgorithmException
{
final String trimmed = email.trim().toLowerCase();
if ( !VALID.matcher( trimmed ).matches() )
{
return "";
}
final int at = trimmed.indexOf( '@' );
final String domain = trimmed.substring( at + 1 );
String local = trimmed.substring( 0, at );
if ( domain.equals( "gmail.com" ) || domain.equals( "googlemail.com" ) )
{
local = local.split( "\\+", 2 )[0].replace( ".", "" );
if ( local.isEmpty() )
{
return "";
}
}
return HexFormat.of().formatHex( MessageDigest.getInstance( "SHA-256" )
.digest( ( local + "@" + domain ).getBytes( StandardCharsets.UTF_8 ) ) );
}
public static void main( final String[] args ) throws NoSuchAlgorithmException
{
System.out.println( normalizeAndHash( " J..A.NE+work.more+extra@GMAIL.COM " ) );
}
}
import { createHash } from "crypto";
function normalizeAndHash(email) {
const trimmed = email.replace(/^[ \t\n\r\f\v]+|[ \t\n\r\f\v]+$/g, "").toLowerCase();
if (!/^[!-?A-~]+@[!-?A-~]+$/.test(trimmed)) {
return "";
}
const at = trimmed.indexOf("@");
let local = trimmed.slice(0, at);
const domain = trimmed.slice(at + 1);
if (domain === "gmail.com" || domain === "googlemail.com") {
local = local.split("+")[0].replaceAll(".", "");
}
return local ? createHash("sha256").update(`${local}@${domain}`).digest("hex") : "";
}
console.log(normalizeAndHash(" J..A.NE+work.more+extra@GMAIL.COM "));
import java.security.MessageDigest
private val VALID = Regex("^[!-?A-~]+@[!-?A-~]+$")
fun normalizeAndHash(email: String): String {
val trimmed = email.trim(' ', '\t', '\n', '\r', '\u000B', '\u000C').lowercase()
if (!VALID.matches(trimmed)) return ""
val (rawLocal, domain) = trimmed.split('@')
val local = if (domain == "gmail.com" || domain == "googlemail.com") {
rawLocal.substringBefore('+').replace(".", "").ifEmpty { return "" }
} else rawLocal
return MessageDigest.getInstance("SHA-256")
.digest("$local@$domain".toByteArray())
.joinToString("") { "%02x".format(it) }
}
fun main() {
println(normalizeAndHash(" J..A.NE+work.more+extra@GMAIL.COM "))
}
import hashlib
import re
VALID = re.compile(r"^[!-?A-~]+@[!-?A-~]+$")
def normalize_and_hash(email):
email = email.strip(" \t\n\r\f\v").lower()
if not VALID.match(email):
return ""
local, _, domain = email.partition("@")
if domain in ("gmail.com", "googlemail.com"):
local = local.split("+", 1)[0].replace(".", "")
return hashlib.sha256(f"{local}@{domain}".encode()).hexdigest() if local else ""
print(normalize_and_hash(" J..A.NE+work.more+extra@GMAIL.COM "))
import CryptoKit
import Foundation
func normalizeAndHash(_ email: String) -> String {
let trimmed = email.trimmingCharacters(
in: CharacterSet(charactersIn: " \t\n\r\u{0b}\u{0c}")).lowercased()
guard trimmed.range(of: "^[!-?A-~]+@[!-?A-~]+$", options: .regularExpression) != nil
else { return "" }
let parts = trimmed.split(separator: "@")
let domain = String(parts[1])
var local = String(parts[0])
if domain == "gmail.com" || domain == "googlemail.com" {
local = local.prefix { $0 != "+" }.filter { $0 != "." }
}
guard !local.isEmpty else { return "" }
return SHA256.hash(data: Data("\(local)@\(domain)".utf8))
.map { String(format: "%02x", $0) }.joined()
}
print(normalizeAndHash(" J..A.NE+work.more+extra@GMAIL.COM "))
988b074286b20c503e3015c2076533f3bf4ce5ca6f8a507ab52c2e0f98d620b7
user_id and email.
AppLovin rejects rows that are missing values for those columns.event_name column must exist and its value must be purchase.value column must contain a non-negative number (≥0).AX — Åland IslandsAD — AndorraAT — AustriaBE — BelgiumDK — DenmarkFO — Faroe IslandsFI — FinlandFR — FranceDE — GermanyGI — GibraltarGR — GreeceGG — GuernseyIS — IcelandIE — IrelandIM — Isle of ManIT — ItalyJE — JerseyLI — LiechtensteinLU — LuxembourgMT — MaltaMC — MonacoNL — NetherlandsNO — NorwayPT — PortugalSM — San MarinoES — SpainSJ — Svalbard & Jan MayenSE — SwedenCH — SwitzerlandGB — United KingdomHere is an example of a valid CSV file:
event_name,user_id,phone,email,event_timestamp,value,currency,transaction_id,country_code,zip,idfv,ifa
purchase,user_abc123,+14155551234,user@example.com,2025-11-10T16:45:00Z,99.99,USD,txn_12345,US,12345,f325g3gb-12fc-352f-c6c3-dz52f0f690d8,918f1d4f-d195-4a8b-af47-44683fe11db9
purchase,user_def456,+14155555678,customer@example.com,2025-11-10T16:45:00Z,149.5,USD,txn_67890,US,12345,f325g3gb-12fc-352f-c6c3-dz52f0f690d8,918f1d4f-d195-4a8b-af47-44683fe11db9
purchase,user_ghi789,+14155559012,shopper@example.com,2025-11-10T17:20:00Z,75,EUR,txn_11223,US,12345,f325g3gb-12fc-352f-c6c3-dz52f0f690d8,918f1d4f-d195-4a8b-af47-44683fe11db9