Supporting Push Notifications
Track user interactions with push notifications to measure re-engagement campaigns and attribute conversions accurately by integrating Apple Push Notification Service (APNs) with the Singular SDK.
Follow the implementation guidelines below to ensure notification data is correctly passed to the Singular SDK for proper attribution.
Why Track Push Notifications: Push notifications drive re-engagement, but tracking requires correct integration. Singular ensures users who interact with notifications are properly attributed, optimizing marketing campaigns and engagement strategies.
Prerequisites: Before APNs tokens are generated and Singular can extract push payloads, verify all of the following are in place:
- The Push Notifications capability is enabled in Xcode under Signing & Capabilities for the app target.
- An APNs key (.p8) or certificate (.p12) has been uploaded to the Singular dashboard for your app.
- The app is built with a provisioning profile that includes the aps-environment entitlement.
- Testing happens on a physical device — APNs tokens are not issued on the iOS Simulator.
-
Singular.start(_:)has run before the SDK can extract a Singular link from a notification payload viapushNotificationLinkPath.
Implementation Guide
Register for Push Notifications
Request authorization from users to receive push notifications and register the app with APNs using UNUserNotificationCenter.
import UserNotifications
// Set the current instance as the delegate for UNUserNotificationCenter
UNUserNotificationCenter.current().delegate = self
// Define the notification authorization options (alert, badge, sound)
let pushAuthOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
// Request notification authorization from the user
UNUserNotificationCenter.current().requestAuthorization(options: pushAuthOptions) { granted, error in
// If an error occurs during authorization, print the error description
if let error = error {
print("registerForPushNotifications : failure - \(error.localizedDescription)")
}
// If the user granted permission, register for remote notifications
if granted {
// Ensure registration is done on the main thread
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
- (void)registerForPushNotifications:(UIApplication *)application {
// Set delegate to self
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
// Define the push notification options
UNAuthorizationOptions pushAuthOptions = UNAuthorizationOptionAlert |
UNAuthorizationOptionBadge |
UNAuthorizationOptionSound;
// Request push notification authorization
[[UNUserNotificationCenter currentNotificationCenter]
requestAuthorizationWithOptions:pushAuthOptions
completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (error) {
NSLog(@"registerForPushNotifications : failure - %@", error.localizedDescription);
}
if (granted) {
dispatch_async(dispatch_get_main_queue(), ^{
[application registerForRemoteNotifications];
});
}
}];
}
Best Practice: Request notification authorization early in the app lifecycle, ideally after demonstrating value to users to improve opt-in rates.
Handle Notification Responses
Process notification responses when users tap push notifications and forward the payload data to Singular for tracking.
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
// Extract the notification payload
let userInfo = response.notification.request.content.userInfo
// Pass the notification data to Singular for tracking
Singular.handlePushNotification(userInfo)
// Call the completion handler to indicate processing is complete
completionHandler()
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler {
// Extract the push notification payload (user info)
NSDictionary *userInfo = response.notification.request.content.userInfo;
// Log the userInfo dictionary for debugging purposes
NSLog(@"didReceiveNotificationResponse userInfo = %@", userInfo);
// Pass the notification data to Singular for tracking
[Singular handlePushNotification:userInfo];
// Call the completion handler to indicate processing is complete
completionHandler();
}
Best Practice: Always call the completion handler promptly to ensure the system knows notification processing is complete. Delayed completion may result in system warnings or throttling.
Configure SDK for Push Payloads
Add push notification payload selectors to your SDK configuration to specify where Singular links are located in the notification data structure.
func getConfig() -> SingularConfig? {
guard let config = SingularConfig(apiKey: "SDK_KEY", andSecret: "SDK_SECRET") else {
return nil
}
// Configure push notification link paths
config.pushNotificationLinkPath = [
["sng_link"],
["rootObj", "nestedObj", "anotherNested", "singularLink"]
]
return config
}
// Start the Singular SDK with the configuration
if let config = getConfig() {
Singular.start(config)
}
- (SingularConfig *)getConfig {
SingularConfig *config = [[SingularConfig alloc] initWithApiKey:@"SDK_KEY"
andSecret:@"SDK_SECRET"];
// Configure push notification link paths
config.pushNotificationLinkPath = @[
@[@"sng_link"],
@[@"rootObj", @"nestedObj", @"anotherNested", @"singularLink"]
];
return config;
}
// Start the Singular SDK with the configuration
SingularConfig *config = [self getConfig];
[Singular start:config];
Selector Configuration:
-
Simple Keys:
Use
["sng_link"]for top-level keys in the payload - Nested Keys: Use odede["rootObj", "nestedObj", "key"] to traverse nested JSON structures
- Multiple Paths: Define multiple selector arrays to check different possible locations for Singular links
Validation Guide
Verify Payload in Start Session
Confirm that push notification links are correctly passed to Singular by inspecting the start session API call.
The Singular SDK includes the push notification payload under the
singular_link
parameter in the start session request when
users tap notifications.
Example Start Session Request:
https://skan.singular.net:443/api/v1/start?
dnt=-1
&update_time=0
&singular_link=https://sl.sng.link/Cclbu/2a7n?_dl=com.singular.app&_smtype=3
&i=com.singular.SwiftScene
&sdk=Singular/12.7.1
&p=iOS
&v=18.2.1
Alternative Verification: Use the Singular SDK Console to verify push notification tracking. Check the Deeplink URL field to confirm the tracking link is captured correctly.
Advanced Configuration
ESP Domain Configuration
Configure external domains if you wrap Singular links within Email Service Provider (ESP) or other third-party domains.
func getConfig() -> SingularConfig? {
guard let config = SingularConfig(apiKey: "SDK_KEY", andSecret: "SDK_SECRET") else {
return nil
}
// Configure ESP domains for wrapped links
config.espDomains = ["sl.esp.link", "custom.domain.com"]
return config
}
- (SingularConfig *)getConfig {
SingularConfig *config = [[SingularConfig alloc] initWithApiKey:@"SDK_KEY"
andSecret:@"SDK_SECRET"];
// Configure ESP domains for wrapped links
config.espDomains = @[@"sl.esp.link", @"custom.domain.com"];
return config;
}
Security Note:
By default, only
sng.link
domains predefined in the Singular Manage Links page are permitted.
Configure ESP domains explicitly if using wrapped links.
Dynamic Deep Link Routing
Implement multiple deep link destinations from a single notification by configuring one Singular tracking link with dynamic redirect overrides.
Use Case Example: A breaking news notification with multiple action options
-
Read Latest News:
newsapp://article?id=12345 -
Trending Topics:
newsapp://trending -
Sports:
newsapp://sports
Instead of creating multiple tracking links, use one Singular link and override redirects dynamically based on user selection. See Overriding Redirects in Singular Tracking Links for implementation details.
Important Considerations
Implementation Notes
-
No Callback Handler:
Unlike
singularLinksHandler, the push notification feature does not provide payload callbacks. Implement your own deep linking logic to route users to specific content within your app -
Attribution Flow:
When users tap notifications,
Singular retrieves the payload and includes it in the start session
event triggered by
Singular.start(). The backend processes this data to attribute the push notification touchpoint and register re-engagement tracking -
Domain Restrictions:
Only Singular link domains
(
sng.link) from the Manage Links page are permitted by default. Configure ESP domains explicitly for wrapped links usingespDomains
Success: By following these steps, your app now tracks push notification interactions with Singular, improving campaign performance insights and ensuring accurate re-engagement attribution.