Clone the sample application repository into a temporary folder to get access to the CleverTap Unreal plugin.
git clone --depth 1 https://github.com/CleverTap/clevertap-unreal-sdk.git
Copy the clevertap-unreal-sdk\Plugins\CleverTap
folder to your Unreal project's Plugin folder.
Add the following to the Plugins
section of your Unreal project's .uproject
file.
{
"Plugins": [
{
"Name": "CleverTap",
"Enabled": true
}
]
}
Add the following to the C++ gameplay module's PrivateDependencyModuleNames
property in each *.Target.cs
file's class constructor.
PrivateDependencyModuleNames.Add("CleverTap");
If you use any of the public CleverTap types in your module's public types then you should add it to the PublicDependencyModuleNames
property instead.
In your project's Config\DefaultEngine.ini
file add the following block at the end.
[/Script/CleverTap.CleverTapConfig]
ProjectId= ;Add your project Id here. This is found on your project's CleverTap dashboard
ProjectToken= ;Add your project token here. This is found on your project's CleverTap dashboard
RegionCode= ;Reference https://developer.clevertap.com/docs/idc#ios to determine what region code string to put here
Alternatively you can edit the Plugin's settings in the UE4Editor.
By default, with bAutoInitializeSharedInstance
set to true
in Config\DefaultEngine.ini
, the UCleverTapSubsystem
will automatically initialize the CleverTap SDK and the default shared instance. It is recommended not to set this to
false
as it is only currently partially supported on iOS.
PII data is stored across the SDK and could be sensitive information. You can enable encryption for PII data such as Email, Identity, Name, and Phone. Currently, two levels of encryption are supported:
- None: All stored data is in plaintext (the default)
- Medium: PII data is encrypted completely
The only way to set the encryption level for the default instance is from Config/DefaultEngine.ini
:
[/Script/CleverTap.CleverTapConfig]
EncryptionLevel=Medium
Different instances can have different encryption levels. To set an encryption level for an additional instance, set the EncryptionLevel
field in the FCleverTapInstanceConfig
used to create it.
CleverTap allows you to send push notifications to your applications from our dashboard, once the user has granted permission in response to a call to PromptForPushPermission()
.
Each platform requires slightly different setup.
To use the default CleverTap notification implementation with Firebase:
-
Follow these instructions to create and register your firebase credentials in the CleverTap dashboard.
-
Copy the generated
google-services.json
to somewhere in your project directory (e.g.Config
). -
In your project's
Config/DefaultEngine.ini
ensurebAndroidIntegrateFirebase
isTrue
andAndroidGoogleServicesJsonPath
is the project-relative path to where you copiedgoogle-services.json
.
[/Script/CleverTap.CleverTapConfig]
bAndroidIntegrateFirebase=True
AndroidGoogleServicesJsonPath=Config/Android/google-services.json
Android requires push notifications to be delivered via pre-defined notification channels. These channels let users customize how different types of notifications behave (e.g., sound, vibration, visibility).
Because channels must be registered during Java's GameApplication.onCreate()
- before Unreal Engine has initialized - they cannot be created dynamically from C++. Instead, they must be preconfigured in your project’s Config/DefaultEngine.ini
.
Define up to 100 channels in your DefaultEngine.ini. Only the ID field is required - this is the unique identifier for the channel.
[/Script/CleverTap.CleverTapConfig]
AndroidNotificationChannelSlot1=ID="general" | Name="General" | Description="General Notifications" | Importance=IMPORTANCE_DEFAULT | bShowBadge=True
AndroidNotificationChannelSlot2=ID="news" | Name="News Updates" | Description="Important news and alerts" | Importance=IMPORTANCE_HIGH | bShowBadge=True | Sound="news_alert.wav"
Valid Importance values: IMPORTANCE_NONE
, IMPORTANCE_MIN
, IMPORTANCE_LOW
, IMPORTANCE_DEFAULT
, IMPORTANCE_HIGH
, IMPORTANCE_MAX
.
The sound files must be included in the APK's res/raw
; see the AndroidSoundsDir
setting for more information.
Note
- The plugin’s
CleverTap_Android_UPL.xml
reads these entries and injects the required Java intoGameApplication.onCreate()
. Errors in theini
syntax will cause the compilation ofGameApplication.java
to fail. - The
AndroidNotificationChannelSlot
settings can only be edited directly in your project’sConfig/DefaultEngine.ini
; they are not available in theProject Settings
GUI.
You can set the channel’s display name and description at runtime using Unreal's localization system. If you use runtime localization, there's no need to specify Name
or Description
in the .ini
.
[/Script/CleverTap.CleverTapConfig]
AndroidNotificationChannelSlot1=ID="general" | Importance=IMPORTANCE_DEFAULT | bShowBadge=True
AndroidNotificationChannelSlot2=ID="news" | Importance=IMPORTANCE_HIGH | bShowBadge=True
CleverTapSys = GEngine->GetEngineSubsystem<UCleverTapSubsystem>();
ICleverTapInstance& CleverTap = CleverTapSys->SharedInstance();
CleverTap.LocalizeAndroidNotificationChannel(TEXT("general"),
NSLOCTEXT("CleverTapSample", "ChannelName_general", "General"),
NSLOCTEXT("CleverTapSample", "ChannelDesc_general", "General Notifications"));
You can define up to 10 notification channel groups to categorize related channels.
Each group is declared in your .ini
, and channels can reference them using Group="group_id"
.
[/Script/CleverTap.CleverTapConfig]
AndroidNotificationChannelGroupSlot1=ID="general" | Name="General"
AndroidNotificationChannelSlot1=ID="general" | Group="general" | Importance=IMPORTANCE_DEFAULT | bShowBadge=True
Group names can be localized at runtime, just like channels.
Call LocalizeAndroidNotificationChannelGroup()
early in startup and again after locale changes.
CleverTapSys = GEngine->GetEngineSubsystem<UCleverTapSubsystem>();
ICleverTapInstance& CleverTap = CleverTapSys->SharedInstance();
CleverTap.LocalizeAndroidNotificationChannelGroup( TEXT("general"),
NSLOCTEXT("CleverTapSample", "ChannelGroupName_general", "General"));
You can define a specific notification channel that CleverTap will use if the channel provided in the push payload is not registered by your app. This ensures that push notifications are displayed consistently even if the app's notification channels are not set up.
In case the SDK does not find the default channel ID specified in the manifest, it will automatically fall back to using a default channel called Miscellaneous
. This ensures that push notifications are still delivered, even if no specific default channel is specified in the manifest.
To specify your app’s preferred default channel:
[/Script/CleverTap.CleverTapConfig]
AndroidDefaultNotificationChannel=general
By default, our SDK uses the app's icon for both the notification icon and the notification bar icon; however, since Android 5, all non-alpha channels are ignored while drawing the main notification icon.
You can supply the project-relative path to an alpha-only png to use instead:
[/Script/CleverTap.CleverTapConfig]
AndroidSmallNotificationIconPath=Config/Android/sample_small_notification_icon.png
Note that the base filename must contain only lowercase letters (a
-z
), digits (0
-9
), or underscores (_
).
For more information about the image requirements, see https://developer.clevertap.com/docs/android-push#set-the-small-notification-icon
Additional Images and Sounds can be included in the APK for direct use by Android & CleverTap.
These are not part of Unreal's regular asset system. They are copied directly into the APK and must follow Android resource rules. Filenames must contain only lowercase letters (a
-z
), digits (0
-9
), or underscores (_
).
Android supports .mp3, .ogg, and .wav files for playing custom sounds.
[/Script/CleverTap.CleverTapConfig]
AndroidImagesDir=Config/Android/Images
AndroidSoundsDir=Config/Android/Sounds
Due to Android’s restriction of allowing only one FirebaseMessagingService
, it cannot coexist cleanly with other Unreal plugins that declare their own FCM service (e.g. the Unreal Firebase plugin).
If you’re already using another Firebase plugin and also require CleverTap push features, you’ll need to disable the CleverTap Firebase integration with bAndroidIntegrateFirebase=False
, and replace the other plug-in’s FCM service with a custom multiplexer implemented in Java.
For example:
public class UnifiedMessagingService extends SomeOtherPluginMessagingService {
@Override
public void onMessageReceived(RemoteMessage message) {
if (isCleverTapMessage(message)) {
new CTFcmMessageHandler().createNotification(getApplicationContext(), message);
} else {
// Let the base class handle it
super.onMessageReceived(message);
}
}
private boolean isCleverTapMessage(RemoteMessage message) {
Map<String, String> data = message.getData();
return data != null && data.containsKey("wzrk_pn"); // CleverTap magic key
}
}
For more information, see Custom Android Push Notification Handling
To handle incoming app links, enable bIntegrateOpenUrl
and define up to four slot-based URL filters in your project’s .ini
file.
[/Script/CleverTap.CleverTapConfig]
bIntegrateOpenUrl=True
; Example Slot1 - catches clevertap-unreal-sample://
DeepLinkSchemeFilterSlot1=clevertap-unreal-sample
; Example Slot2 - catches clevertap://unreal.com/sample
DeepLinkSchemeFilterSlot2=clevertap
AndroidIntentFilterSlot2_Host=unreal.com
AndroidIntentFilterSlot2_PathPrefix=/sample
; Example Slot3 - catches http://clevertap.com/unreal-sample,
; but requires digital verification that the app is approved for this domain
; See https://developer.android.com/training/app-links/verify-android-applinks
DeepLinkSchemeFilterSlot3=http
AndroidIntentFilterSlot3_Host=clevertap.com
AndroidIntentFilterSlot3_PathPrefix=/unreal-sample
AndroidIntentFilterSlot3_AutoVerify=True
[!NOTE] Http/Https schemes require digital verification via
assetlinks.json
hosted on the target domain. See: https://developer.android.com/training/app-links/verify-android-applinks
You can simulate clicking on a deep link with adb
:
adb shell am start -a android.intent.action.VIEW -d "clevertap-unreal-sample://test/path"
Disable OpenUrlActivity integration if you need full control over your app’s deep link handling, and plan to define your own activity with complex intent filters via custom UPL rules.
If your custom activity forwards the intent to the GameActivity
, it will still be routed to the OnOpenURL
delegate.
To handle incoming app links, enable bIntegrateOpenUrl
and define up to four slot-based URL scheme filters in your project’s .ini
file.
[/Script/CleverTap.CleverTapConfig]
bIntegrateOpenUrl=True
; Example Slot1 - catches clevertap-unreal-sample://
DeepLinkSchemeFilterSlot1=clevertap-unreal-sample
; Example Slot2 - catches clevertap://
DeepLinkSchemeFilterSlot2=clevertap
; Example Slot3 - catches http://
DeepLinkSchemeFilterSlot3=http
To further filter the allowed links use the RegisterCleverTapUrlHandler()
method to register a filter or do custom
URL handling.
- Follow the CleverTap guide to set up APNs for your app.
- In your project's
Config/DefaultEngine.ini
ensurebEnableRemoteNotificationsSupport
isTrue
in the[/Script/IOSRuntimeSettings.IOSRuntimeSettings]
section. - If you would like push notifications to appear when your app is in the foreground make sure that
bIOSPresentPushNotificationsInForeground
is set toTrue
in the[/Script/CleverTap.CleverTapConfig]
section of your project'sConfig/DefaultEngine.ini
Note
The CleverTap Unreal plugin currently does not support Push Impressions, Push Primers, or Rich Push Notifications.
On Windows, a patch program equivalent can be installed using winget.
winget install GnuWin32.Patch
If you're App isn't in the foreground or background then Unreal will not forward the push notification to CleverTap unless you make engine changes to Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSAppDelegate.cpp
. An example of such changes can be found in the EnginePatches/iOSSavedRemoteNotifications.patch
patch file. This can be applied in the Unreal root directory using the linux patch command.
UnrealEngine % patch -p1 -u -i /path/to/CleverTapSample/EnginePatches/iOSSavedRemoteNotifications.patch
Rich Push Notifications require a Notification Service Extension. Unfortunately Unreal Engine doesn't support app extensions without modification to its build process. An example patch that adds support for app extensions can be found in the EnginePatches/UE4.27_ExtensionSupport.patch patch file. This can be applied in the Unreal root directory using the linux patch command.
UnrealEngine % patch -p1 -u -i /path/to/CleverTapSample/EnginePatches/UE4.27_ExtensionSupport.patch
Once patched, the extension located at Plugins/CleverTap/Source/ThirdParty/IOS/Extensions/CTNotificationService will be included in the build. A custom signing provision can be specified for the extension via changes to the Config/DefaultEngine.ini
file.
[/Script/IOSRuntimeSettings.IOSRuntimeSettings]
MobileProvision_CTNotificationService=YourProvisioning.mobileprovision
Please make sure the specified .mobileprovision file is installed before building.
Note
This engine patch adds support for arbitrary app extensions located in {ProjectFolder}/Build/IOS/Extensions
or any
{ProjectFolder}/Plugins/{PluginNameHere}/Source/ThirdParty/IOS/Extensions
folders. It only supports UE4.27 and has
only been tested against the CTNotificationService extension provided here.
After setting up your shared CleverTap instance the OnPushNotificationClicked
delegate can be enabled by calling EnableOnPushNotificationClicked()
. On iOS, if the engine patch has been applied for push notifications that launch the app then this can trigger attempting to broadcast the push notification that launched the app. Therefore a delegate listener should be added before calling EnableOnPushNotificationClicked()
.
CleverTapSys = GEngine->GetEngineSubsystem<UCleverTapSubsystem>();
ICleverTapInstance& CleverTap = CleverTapSys->SharedInstance();
CleverTap.OnPushNotificationClicked.AddLambda([](const FCleverTapProperties& NotificationPayload)
{
UE_LOG(LogTemp, Log, TEXT("Push notification was tapped"));
});
CleverTap.EnableOnPushNotificationClicked();
In-app notifications are initially suspended. When your callbacks are set up then call
ICleverTapInstance::ResumeInAppNotifications()
to begin receiving in-app notifications.
The payload parameters for all in-app notifications callbacks have their nested objects flattened as dot separated keys
in the property map so that the object {"a": { "b": 3.14 }}
would have a single entry of
Payload[FString{TEXT("a.b")}] == FCleverTapPropertyValue{ 3.14 }
.
When an in-app notification is displayed this callback will be invoked with and the notification payload is passed as a parameter.
CleverTapSys = GEngine->GetEngineSubsystem<UCleverTapSubsystem>();
ICleverTapInstance& CleverTap = CleverTapSys->SharedInstance();
CleverTap.OnInAppNotificationShown.AddLambda([](const FCleverTapProperties& Payload)
{
UE_LOG(LogTemp, Log, TEXT("An in-app notification was shown: %s"), *ToDebugString(Payload));
});
When the user clicks on an in-app notification button this callback will be invoked with the key/value pairs associated with that button.
CleverTapSys = GEngine->GetEngineSubsystem<UCleverTapSubsystem>();
ICleverTapInstance& CleverTap = CleverTapSys->SharedInstance();
CleverTap.OnInAppNotificationButtonClicked.AddLambda([](const FCleverTapProperties& Payload)
{
UE_LOG(LogTemp, Log, TEXT("An in-app notification button clicked: %s"), *ToDebugString(Payload));
});
When an in-app notification is dismissed by the user this callback will be invoked with the notification payload and any extra key/value pairs specified for the in-app notification in the CleverTap dashboard.
CleverTapSys = GEngine->GetEngineSubsystem<UCleverTapSubsystem>();
ICleverTapInstance& CleverTap = CleverTapSys->SharedInstance();
CleverTap.OnInAppNotificationDismissed.AddLambda([](const FCleverTapProperties& Extras, const FCleverTapProperties& ActionExtras)
{
UE_LOG(LogTemp, Log, TEXT("An in-app notification was dismissed. Extras: %s, ActionExtras: %s"),
*ToDebugString(Extras), *ToDebugString(ActionExtras));
});
In-app notifications initially start suspended. If you like to discard any notifications that were queued while they
were suspended then you can invoke ICleverTapInstance::DiscardInAppNotifications()
to do so. Notifications can be
resumed by calling ICleverTapInstance::ResumeInAppNotifications()
. Any queued notification that happened while
suspended, and which has not been explicitly discarded, will be presented to the user. If you would like to suspend
notifications again then call ICleverTapInstance::SuspendInAppNotifications()
.
The OnUserLogin()
method can be used when a user is identifier and logs into the app. Upon first login this enriches the
initial "Anonymous" user profile with additional properties such as name, age, and email. See Updating the User Profile for a list of predefined properties.
FCleverTapProperties Profile;
Profile.Add(TEXT("Name"), TEXT("Jack Montana")); // String
Profile.Add(TEXT("Identity"), 61026032); // String or Number
Profile.Add(TEXT("Email"), TEXT("[email protected]")); // Email string
Profile.Add(TEXT("Phone"), TEXT("+14155551234")); // with country code, starting with +
Profile.Add(TEXT("Gender"), TEXT("M")); // Can be either M or F
Profile.Add(TEXT("DOB"), FCleverTapDate(1953, 3, 13));
Profile.Add(TEXT("MSG-email"), false); // Disable email notifications
Profile.Add(TEXT("MSG-push"), true); // Enable push notifications
Profile.Add(TEXT("MSG-sms"), false); // Disable SMS notifications
Profile.Add(TEXT("MSG-whatsapp"), true); // Enable WhatsApp notifications
TArray<FString> Stuff;
Stuff.Add(TEXT("bag"));
Stuff.Add(TEXT("shoes"));
Profile.Add(TEXT("MyStuff"), Stuff); // Multi-Value array of string support
ICleverTapInstance& CleverTap = GEngine->GetEngineSubsystem<UCleverTapSubsystem>()->SharedInstance();
CleverTap.OnUserLogin(Profile);
The user's profile can be enriched with additional properties at any time using the PushProfile()
method. This
supports arbitrary single value and multi-value properties like OnUserLogin()
.
FCleverTapProperties Profile;
// Update an existing property
Profile.Add(TEXT("MSG-push"), false);
// Add new properties
Profile.Add(TEXT("Tz"), TEXT("Asia/Kolkata"));
Profile.Add(TEXT("Plan Type"), TEXT("Silver"));
Profile.Add(TEXT("Score"), 100);
ICleverTapInstance& CleverTap = GEngine->GetEngineSubsystem<UCleverTapSubsystem>()->SharedInstance();
CleverTap.PushProfile(Profile);
If a given profile property is a int32
, int64
, float
, or double
then it can be incremented or decremented by an
arbitrary positive value.
ICleverTapInstance& CleverTap = GEngine->GetEngineSubsystem<UCleverTapSubsystem>()->SharedInstance();
CleverTap.IncrementValue(TEXT("Score"), 50);
CleverTap.DecrementValue(TEXT("Score"), 100);
CleverTap.IncrementValue(TEXT("Score"), 5.5);
CleverTap.DecrementValue(TEXT("Score"), 3.14);
User Events can be recorded any time after initialization.
ICleverTapInstance& CleverTap = GEngine->GetEngineSubsystem<UCleverTapSubsystem>()->SharedInstance();
// event without properties
CleverTap.PushEvent(TEXT("Event No Props"));
// event with properties
FCleverTapProperties Actions;
Actions.Add("Product Name", "Casio Chronograph Watch");
Actions.Add("Category", "Mens Accessories");
Actions.Add("Price", 59.99);
CleverTap.PushEvent(TEXT("Product viewed"), Actions);
Event values can be any type that the FCleverTapPropertyValue
variant type supports (int32
, int64
, double
, float
, bool
, const ANSICHAR*
, FString
, or FCleverTapDate
).
Charged events are a special user event to record transaction details of a purchase. Each item in the purchase can be recorded and enriched with custom properties.
// charge event
FCleverTapProperties ChargeDetails;
ChargeDetails.Add("Amount", 300);
ChargeDetails.Add("Payment Mode", "Credit card");
ChargeDetails.Add("Charged ID", 24052014);
FCleverTapProperties Item1;
Item1.Add("Product category", "books");
Item1.Add("Book name", "The Millionaire next door");
Item1.Add("Quantity", 1);
FCleverTapProperties Item2;
Item2.Add("Product category", "books");
Item2.Add("Book name", "Achieving inner zen");
Item2.Add("Quantity", 1);
FCleverTapProperties Item3;
Item3.Add("Product category", "books");
Item3.Add("Book name", "Chuck it, let's do it");
Item3.Add("Quantity", 5);
TArray<FCleverTapProperties> Items;
Items.Add(Item1);
Items.Add(Item2);
Items.Add(Item3);
ICleverTapInstance& CleverTap = GEngine->GetEngineSubsystem<UCleverTapSubsystem>()->SharedInstance();
CleverTap.PushChargedEvent(ChargeDetails, Items);