Tracking In-App Events
Track in-app events to analyze campaign performance and measure key performance indicators (KPIs) such as user logins, registrations, tutorial completions, or progression milestones.
Standard Events and Attributes
Understanding Event Types
Singular supports two types of events to accommodate both universal and app-specific tracking needs.
-
Standard Events: Predefined events (e.g.,
sngLogin,sngContentView) recognized by Singular and supported by ad networks for reporting and optimization. Using standard events simplifies setup, as Singular automatically adds them to your Events list without manual definition. See the List of Standard Events and Attributes for complete event names and recommended attributes. -
Custom Events: Events unique to your app (e.g.,
Signup,AchievementUnlocked) that don't match Singular's standard events.
Recommendation: Use standard events whenever possible for compatibility with ad networks and automatic recognition in Singular's Events list.
Your UA, marketing, or business team should compile the list of events based on your organization's marketing KPIs. Reference the guide How to Track In-App Events: Guide for Singular Attribution Customers for planning.
Custom Event Limitations
Custom events have specific character and encoding constraints to ensure compatibility with third-party partners and analytics solutions.
Custom Event Limitations:
- Language: Pass event names and attributes in English to ensure compatibility with third-party partners and analytics solutions
- Event Names: Limited to 32 ASCII characters. Non-ASCII strings must be under 32 bytes when converted to UTF-8
- Attributes and Values: Limited to 500 ASCII characters
Sending Events
Event Method
Track simple events without additional attributes using the
event() method.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
// Track a simple custom event
NativeSingular.event('SignUp');
// Track a standard event using constant
NativeSingular.event('sngLogin');
import { Singular } from 'singular-react-native';
// Track a simple custom event
Singular.event('SignUp');
// Track a standard event using constant
Singular.event('sngLogin');
Method Signature:
static event(eventName: string): void
For the complete list of methods, see event method reference.
EventWithArgs Method
Track events with additional custom attributes to provide richer context and enable detailed segmentation in reports.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
// Track custom event with attributes
NativeSingular.eventWithArgs('LevelComplete', {
level: 5,
score: 1250,
time_spent: 45.3
});
// Track standard event with recommended attributes
NativeSingular.eventWithArgs('sngTutorialComplete', {
sngAttrContent: 'React Native Basics',
sngAttrContentId: '32',
sngAttrContentType: 'video',
sngAttrSuccess: 'yes'
});
import { Singular } from 'singular-react-native';
// Track custom event with attributes
Singular.eventWithArgs('LevelComplete', {
level: 5,
score: 1250,
time_spent: 45.3
});
// Track standard event with recommended attributes
Singular.eventWithArgs('sngTutorialComplete', {
sngAttrContent: 'React Native Basics',
sngAttrContentId: '32',
sngAttrContentType: 'video',
sngAttrSuccess: 'yes'
});
Method Signature:
static eventWithArgs(eventName: string, args: Record<string, any>): void
For the complete list of methods, see eventWithArgs method reference.
Best Practices
- Use Standard Events: Prefer standard events for compatibility with ad networks and automatic recognition in Singular's Events list
- Validate Attributes: Check that attributes match the expected format and character limits before sending
- Debug Events: Enable SDK logging during development to verify events are sent correctly and triggered at the appropriate moments
- Coordinate with Teams: Work with your UA/marketing team to ensure tracked events align with your app's KPIs
- Test Before Production: Test events in a development environment to verify data accuracy in the Singular Dashboard
Tracking In-App Revenue
Track revenue from in-app purchases (IAP), subscriptions, and custom revenue sources to measure campaign performance and return on ad spend (ROAS).
Revenue data flows through three channels:
- Interactive Reports: View revenue metrics in the Singular dashboard
- Export Logs: Access detailed ETL data for custom analysis
- Real-Time Postbacks: Send revenue events to external platforms
Why Track Revenue Events?
- Rich Analytics: Capture detailed transaction data to enhance Singular reports
- Fraud Prevention: Include transaction receipts (e.g., from Google Play or Apple App Store) to validate purchases and combat in-app fraud
- Campaign Optimization: Measure ROI by tying revenue to marketing efforts
Best Practice: Pass the Full Purchase Object
We strongly recommend passing the purchase object returned from Android's (Google Play Billing) or iOS's (StoreKit) In-App Purchase (IAP) process. This ensures Singular receives comprehensive transaction details, including:
- Product ID
- Price
- Currency
- Transaction ID
- Receipt data (for validation)
By passing the full purchase object, you enable richer reporting and leverage Singular's fraud detection capabilities, particularly for Google Play transactions.
In-App Purchase Integration
Capture the IAP Purchase Object
Use platform-specific IAP libraries to retrieve the purchase object with complete transaction details.
- iOS: Use react-native-iap or similar to access StoreKit purchase details
- Android: Use react-native-iap or the Google Play Billing Library to obtain the purchase object
InAppPurchase Method
Track in-app purchase events with purchase details for revenue validation and fraud prevention.
Method Signatures:
static inAppPurchase(eventName: string, purchase: SingularIOSPurchase | SingularAndroidPurchase): void
static inAppPurchaseWithArgs(eventName: string, purchase: SingularIOSPurchase | SingularAndroidPurchase, args: Record<string, any>): void
For the complete list of methods, see inAppPurchase method reference.
Complete IAP Implementation Example
Implement a complete purchase listener that captures IAP events and sends them to Singular with platform-specific purchase objects.
// TurboModule direct API (React Native 0.76+ New Architecture)
import { Platform } from 'react-native';
import NativeSingular from 'singular-react-native/js/NativeSingular';
import {
SingularIOSPurchase,
SingularAndroidPurchase
} from 'singular-react-native';
import {
initConnection,
getProducts,
setPurchaseListener,
finishTransaction,
IAPResponseCode
} from 'react-native-iap';
// Helper function to convert price to a double
const getPriceAsDouble = (product) => {
if (Platform.OS === 'android' && product.priceAmountMicros) {
// Android: Convert priceAmountMicros to double
return product.priceAmountMicros / 1000000.0;
} else if (Platform.OS === 'ios') {
// iOS: Parse price string (e.g., "$4.99" -> 4.99)
const priceString = product.price.replace(/[^0-9.]/g, '');
return parseFloat(priceString) || 0.0;
}
return 0.0; // Fallback
};
// Set up purchase listener
const handlePurchases = async () => {
try {
// Initialize connection
await initConnection();
// Set purchase listener
setPurchaseListener(async ({ responseCode, results }) => {
if (responseCode === IAPResponseCode.OK) {
for (const purchase of results) {
if (!purchase.acknowledged) {
// Fetch product details
const products = await getProducts({ skus: [purchase.productId] });
const product = products.length > 0 ? products[0] : null;
if (!product) return;
// Create purchase object
let singularPurchase = null;
if (Platform.OS === 'ios') {
singularPurchase = new SingularIOSPurchase(
getPriceAsDouble(product),
product.currency ?? 'USD',
purchase.productId,
purchase.transactionId ?? '',
purchase.transactionReceipt
);
} else if (Platform.OS === 'android') {
const jsonData = JSON.parse(purchase.transactionReceipt || '{}');
const receipt = jsonData.purchaseToken || '';
singularPurchase = new SingularAndroidPurchase(
getPriceAsDouble(product),
product.currency ?? 'USD',
purchase.transactionReceipt,
purchase.signatureAndroid
);
} else {
return;
}
// Track in-app purchase
NativeSingular.inAppPurchase('iap_purchase', singularPurchase);
// Complete the purchase
await finishTransaction(purchase, Platform.OS === 'ios');
}
}
}
});
} catch (error) {
console.error('Error handling purchases:', error);
}
};
// Call handlePurchases when your app starts
handlePurchases();
import { Platform } from 'react-native';
import {
Singular,
SingularIOSPurchase,
SingularAndroidPurchase
} from 'singular-react-native';
import {
initConnection,
getProducts,
setPurchaseListener,
finishTransaction,
IAPResponseCode
} from 'react-native-iap';
// Helper function to convert price to a double
const getPriceAsDouble = (product) => {
if (Platform.OS === 'android' && product.priceAmountMicros) {
// Android: Convert priceAmountMicros to double
return product.priceAmountMicros / 1000000.0;
} else if (Platform.OS === 'ios') {
// iOS: Parse price string (e.g., "$4.99" -> 4.99)
const priceString = product.price.replace(/[^0-9.]/g, '');
return parseFloat(priceString) || 0.0;
}
return 0.0; // Fallback
};
// Set up purchase listener
const handlePurchases = async () => {
try {
// Initialize connection
await initConnection();
// Set purchase listener
setPurchaseListener(async ({ responseCode, results }) => {
if (responseCode === IAPResponseCode.OK) {
for (const purchase of results) {
if (!purchase.acknowledged) {
// Fetch product details
const products = await getProducts({ skus: [purchase.productId] });
const product = products.length > 0 ? products[0] : null;
if (!product) return;
// Create purchase object
let singularPurchase = null;
if (Platform.OS === 'ios') {
singularPurchase = new SingularIOSPurchase(
getPriceAsDouble(product), // Price as double, e.g., 4.99
product.currency ?? 'USD', // Currency code, e.g., "USD"
purchase.productId, // e.g., "com.example.premium_subscription"
purchase.transactionId ?? '', // e.g., "1000000823456789"
purchase.transactionReceipt // JWS receipt from StoreKit
);
} else if (Platform.OS === 'android') {
// Parse transactionReceipt to extract purchaseToken
const jsonData = JSON.parse(purchase.transactionReceipt || '{}');
const receipt = jsonData.purchaseToken || '';
singularPurchase = new SingularAndroidPurchase(
getPriceAsDouble(product), // Price as double, e.g., 4.99
product.currency ?? 'USD', // Currency code, e.g., "USD"
purchase.transactionReceipt, // Full JSON receipt from Google Play
purchase.signatureAndroid // Signature from Google Play
);
} else {
return; // Unsupported platform
}
// Track in-app purchase
Singular.inAppPurchase('iap_purchase', singularPurchase);
// Complete the purchase
await finishTransaction(purchase, Platform.OS === 'ios');
}
}
}
});
} catch (error) {
console.error('Error handling purchases:', error);
}
};
// Call handlePurchases when your app starts
handlePurchases();
Manual Revenue Tracking
Revenue without Purchase Validation
Track revenue by passing currency, amount, and optional product details without the Purchase object. Note that this method does not provide transaction receipts for validation.
Important: When sending revenue events without a
valid purchase object, Singular does not validate the transactions.
We strongly recommend using the inAppPurchase() methods
described above whenever possible.
Note: Pass currency as a three-letter ISO 4217 currency
code, e.g., USD, EUR, INR.
Revenue Method
Track simple revenue events with a specified currency and amount.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
// Track revenue without product details
NativeSingular.revenue('USD', 4.99);
import { Singular } from 'singular-react-native';
// Track revenue without product details
Singular.revenue('USD', 4.99);
Method Signature:
static revenue(currency: string, amount: number): void
For the complete list of methods, see revenue method reference.
RevenueWithArgs Method
Track revenue events with a specified currency, amount, and additional custom attributes.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
// Track revenue with attributes
NativeSingular.revenueWithArgs('USD', 9.98, {
productSKU: 'coin_package_abc123',
productName: 'Coin Pack 10',
productCategory: 'Bundles',
productQuantity: 2,
productPrice: 4.99,
transaction_id: 'T12345'
});
import { Singular } from 'singular-react-native';
// Track revenue with attributes
Singular.revenueWithArgs('USD', 9.98, {
productSKU: 'coin_package_abc123',
productName: 'Coin Pack 10',
productCategory: 'Bundles',
productQuantity: 2,
productPrice: 4.99,
transaction_id: 'T12345'
});
Method Signature:
static revenueWithArgs(currency: string, amount: number, args: Record<string, any>): void
For the complete list of methods, see revenueWithArgs method reference.
CustomRevenue Method
Track custom revenue events with a specified event name, currency, and amount.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
// Track custom revenue event
NativeSingular.customRevenue('PremiumUpgrade', 'USD', 9.99);
import { Singular } from 'singular-react-native';
// Track custom revenue event
Singular.customRevenue('PremiumUpgrade', 'USD', 9.99);
Method Signature:
static customRevenue(eventName: string, currency: string, amount: number): void
For the complete list of methods, see customRevenue method reference.
CustomRevenueWithArgs Method
Track custom revenue events with a specified event name, currency, amount, and additional custom attributes.
// TurboModule direct API (React Native 0.76+ New Architecture)
import NativeSingular from 'singular-react-native/js/NativeSingular';
// Track custom revenue event with attributes
NativeSingular.customRevenueWithArgs('PremiumBundlePurchase', 'USD', 99.99, {
productSKU: 'premium_bundle_xyz',
productName: 'Premium Bundle',
productCategory: 'Bundles',
productQuantity: 1,
productPrice: 99.99,
discount_applied: true
});
import { Singular } from 'singular-react-native';
// Track custom revenue event with attributes
Singular.customRevenueWithArgs('PremiumBundlePurchase', 'USD', 99.99, {
productSKU: 'premium_bundle_xyz',
productName: 'Premium Bundle',
productCategory: 'Bundles',
productQuantity: 1,
productPrice: 99.99,
discount_applied: true
});
Method Signature:
static customRevenueWithArgs(eventName: string, currency: string, amount: number, args: Record<string, any>): void
For the complete list of methods, see customRevenueWithArgs method reference.
Subscription Revenue
Tracking Subscriptions
Singular offers a comprehensive guide on implementing subscription events using the Singular SDK. The guide covers in-app subscription event tracking across various platforms.
- Read the Subscription Event Technical Implementation Guide if you would like to track subscription revenue
Hybrid Event Tracking (Advanced)
Singular recommends sending all events and revenue through the Singular SDK integrated into your app for optimal attribution. However, Singular can collect events from other sources when necessary.
Events sent outside the Singular SDK must comply with Singular's Server-to-Server Event documentation requirements and provide matching device identifiers for correct attribution.
Important:
Discrepancies will occur if device identifiers used on Server-to-Server event requests do not have a matching device identifier in Singular. Be aware of the following possibilities:
- Early Events: If an event request is received before the Singular SDK has recorded the device identifier from an App Session, the event request will be considered the "first session" for the unknown device, and Singular will attribute the device as an organic attribution
- Mismatched Identifiers: If the Singular SDK recorded a device identifier, but it differs from the device identifier specified in the Server-to-Server Event request, then the event will be attributed incorrectly
Hybrid Event Tracking Guides
Sending Events from an Internal Server
Collect revenue data from your internal server to analyze campaign performance and ROI.
Requirements:
- Capture Device Identifiers: From an in-app Registration or Login Event, capture and pass the device identifiers and store this data with the User ID on your server. Because device identifiers may change for a user, update the identifiers when a user generates an app session. This guarantees the server-side event will be attributed to the correct device
- Platform-Specific Identifiers: Server-side events are platform specific and should only be sent with the device identifier matching the device platform (e.g., IDFA or IDFV for iOS devices, GAID for Android devices)
- Real-Time Updates: Use the Singular Internal BI postback mechanism to push an event in real time to your internal endpoint so that you can update the data set on the server side. See the Internal BI Postback FAQ
- Implementation Details: Review the Tracking Revenue section in the Server-to-Server Integration guide for details
Sending Events from a Revenue Provider
Integrate third-party revenue providers like RevenueCat or adapty to send purchase and subscription revenue to Singular.
Supported Providers:
- RevenueCat: Learn more in the RevenueCat documentation
- adapty: Learn more in the adapty documentation
Sending Events from Segment
Enable Segment to send events to Singular in parallel with the Singular SDK by adding a "Cloud-Mode" destination in Segment.
Follow the implementation guide Singular-Segment Integration for detailed setup instructions.