Unity SDK Integration
The Unity SDK wraps the native Joryio iOS and Android SDKs. It adds no tracking logic of its own: every call forwards to the native SDK, so behaviour, batching and offline handling are identical to a native integration.
Requirements
- Unity 2022.3 LTS or newer. Unity 2021.3 and 2022.1 build Android with JDK 8 and cannot compile against the native Android SDK (Java 11 bytecode).
- iOS 14.0+ / Android 6.0+ (API 23)
- Android: Target API Level 34 or higher (Player Settings). The native SDK's AndroidX libraries require compileSdk 34, and Unity's Android build fails below it.
- Android: External Dependency Manager for Unity (EDM4U). iOS does not use it (see Installation).
- iOS: Xcode 15 or newer, with network access to
github.comwhen the exported project is first built - Xcode downloads the native SDK as a Swift package. - For Android push: Firebase Cloud Messaging in the game - the Firebase Unity SDK's Messaging package and your
google-services.json. The native Android SDK compiles againstfirebase-messagingbut does not bring it; without it, analytics and in-app messaging work and push stays off.
iOS and Android only. On other platforms - the Editor, desktop, consoles - every
method exists and does nothing, and the SDK logs one warning per session. Your
game code never needs #if UNITY_IOS || UNITY_ANDROID around a Joryio call.
Installation
Add the package (com.joryio.sdk) from Git: in the Package Manager choose
+ → Add package from git URL and enter the URL with the release tag:
https://github.com/joryio/joryio-unity-sdk.git#1.3.3
To update, change the tag to the new version; always pin one, because without a tag the Package Manager follows the newest commit.
Or download joryio-unity-<version>.unitypackage from the
GitHub releases page and
import it with Assets → Import Package → Custom Package. The SDK lands under
Assets/JoryioSDK; to update, delete that folder and import the new version.
Use one install method, not both. The Git URL package and Assets/JoryioSDK
carry the same assemblies (Joryio.Runtime, Joryio.Editor) and the same asset
GUIDs, so a project with both does not compile. To switch, remove the one you
have first: delete Assets/JoryioSDK, or remove the Joryio package in the
Package Manager. The .unitypackage brings the licence terms (LICENSE),
README.md and CHANGELOG.md into Assets/JoryioSDK as well.
The package is not on OpenUPM, which lists open-source packages only; the Joryio SDKs are licensed commercially.
The package ships no prebuilt native binaries, so shared libraries are reconciled with whatever else your project already uses. It brings the UI-free native SDKs - tracking, identity and push, but not the native in-app message views (see In-app messages):
- Android: let EDM4U resolve
io.joryio:joryio-androidthrough Gradle, from the package's dependency manifest (Editor/JoryioDependencies.xml). - iOS: nothing to resolve in Unity. When you build for iOS, the package adds
the native SDK to the exported Xcode project as a Swift package
(
https://github.com/joryio/joryio-ios-sdk, at exactly the package's version) and links itsJoryioproduct intoUnityFramework. Xcode downloads and resolves it the first time it opens or builds the project. Neither CocoaPods nor EDM4U is involved on iOS.
Earlier versions took the iOS SDK from CocoaPods (the Joryio/Core pod), and
CocoaPods trunk stops accepting new versions on 2 December 2026. After
upgrading, build the iOS player with Replace rather than Append once, so
the exported project no longer carries the old pod, and delete any
<iosPod name="Joryio/..."> line from your own EDM4U dependency files.
Quick start
Each platform has its own SDK key (jry_sdk_ios_..., jry_sdk_android_...),
from Settings → Apps in the dashboard. Use the API host shown for your
workspace.
using Joryio.Runtime;
void Awake()
{
#if UNITY_IOS
const string sdkKey = "jry_sdk_ios_YOUR_IOS_KEY";
#else
const string sdkKey = "jry_sdk_android_YOUR_ANDROID_KEY";
#endif
JoryioSDK.Initialize(sdkKey, "api-eu1.joryio.com");
}
Then track what players do:
JoryioSDK.Track("level_complete", new Dictionary<string, object> {
{ "level", 7 },
{ "duration_seconds", 143.5 },
});
JoryioSDK.Identify("player-1234");
JoryioSDK.SetAttribute("favourite_class", "mage");
JoryioSDK.IncrementAttribute("games_played");
// Another identifier for the same player - works before Identify too
JoryioSDK.AddAlias("steam_id", "76561198000000000");
Values keep their type across the boundary: { "level", 7 } is stored as the
number 7, not "7". Segments compare numerically, so a stringified number would
match nobody. Every numeric type is kept, nested dictionaries and lists stay
objects and arrays, DateTime is sent as ISO-8601 in UTC, and inside event
properties NaN or infinity becomes null. As an attribute value (SetAttribute,
SetAttributes, IncrementAttribute) NaN or infinity is ignored with a
warning and the attribute is left unchanged - it is not unset.
No call throws into your game: a call before Initialize, a null required
argument or a native failure is logged and ignored. A null attribute value is
not an error: SetAttribute("clan", null) unsets the attribute, the same as
UnsetAttribute("clan"), on iOS and Android alike.
The exception is consent: OptOut(), OptIn() and UpdateConsent() work before Initialize. The decision is applied when the SDK starts, before anything is sent. IsUserOptedOut() also answers before Initialize - see Consent. WipeData() and GetTrackingConsent() work before Initialize too, and so do SetSdkAuthenticationToken and SetSdkAuthenticationErrorHandler - see SDK authentication. So do subscription changes and UnregisterPush(): SetSubscription, AddToSubscriptionGroup, RemoveFromSubscriptionGroup and UnregisterPush() made before Initialize are held, kept on the device across a restart, and sent once the SDK starts. A newer change of the same channel or group replaces an older one, so the player's last choice is the one sent. A withdrawal (an unsubscribe, leaving a group, UnregisterPush()) is always sent: an OptOut(), a Denied consent or WipeData() before Initialize discards held subscribes, never withdrawals, and a withdrawal made before a WipeData() still goes out for the player who made it, not for the new identity the wipe creates. A RegisterPushToken before Initialize is not sent, and it does not cancel a held UnregisterPush(). A subscribe, an opt-in or a group join made before Initialize while the player is opted out or consent is Denied (stored from an earlier launch or set in this one) is not held at all: it is dropped with a warning, and a later OptIn() or Granted does not send it.
Configuration
Every setting is optional and mirrors the native SDKs' option of the same name. Durations are seconds and say so in their name, because the natives disagree (iOS takes seconds, Android milliseconds) - the bridges convert.
JoryioSDK.Initialize(sdkKey, "api-eu1.joryio.com", new JoryioConfig {
Debug = UnityEngine.Debug.isDebugBuild,
FlushIntervalSeconds = 10,
AnonymousId = existingDeviceId, // only if you already have one
});
| Property | Type | Default | Description |
|---|---|---|---|
Debug | bool | false | Verbose native logging. The native log level follows it: debug when true, errors only otherwise. |
BatchSize | int | 50 | Events per batch before an automatic flush. |
FlushIntervalSeconds | double | 5 | Seconds between automatic flushes. |
SendImmediately | bool | false | Send each event as soon as it is tracked, without batching (not recommended for production). |
MaxQueueSize | int | 1000 | Most events the local queue holds while flushes fail; beyond it the oldest are dropped. |
RequestTimeoutSeconds | double | 10 | Per-request HTTP timeout. |
SessionTimeoutSeconds | double | 1800 | Seconds of inactivity before a new session starts. |
TrackSessionStart | bool | true | Emit a session start event automatically. |
AnonymousId | string | generated | Your own anonymous id for this device. Used for this run and stored only when nothing is stored yet, so an id set once survives the game dropping the option later. |
RequestPushPermissionAtLaunch | bool | false | Ask for the push permission during Initialize. See below for why the default is off. |
MaxRetries | int | 3 | Retries per request before it waits for the next flush. |
RetryBackoffMs | int | 1000 | Base delay between retries, in milliseconds (the native SDKs' name). |
TrackingConsent | TrackingConsent? | null (not set) | Your consent screen's answer at launch. Not set keeps the answer the SDK remembered (a stored Denied stays denied) and otherwise tracks normally. Granted tracks and replaces a stored denial; Pending sends nothing until UpdateConsent(TrackingConsent.Granted), for this launch only; Denied collects nothing and is remembered. See Consent. |
OptOut | bool | false | Start opted out. An opt-out is stored, so false does not undo an earlier OptOut(). |
NotificationChannelName | string | null ("Notifications") | Android only. The notification channel's name in the system's notification settings (Android 8+); pass it localised. iOS has no channels and ignores it. |
EnableSdkAuthentication | bool | false | Send a signed token with requests that name a user. See SDK authentication. |
SdkAuthenticationToken | string | none | The token to start with, if you already have one. |
AllowHtmlJsInAppMessages | bool | false | Let the native UI draw HTML messages. Scripts in them do not run; the native SDK's bridge handles buttons and forms. No effect on messages your own handler renders. |
HoldInAppMessagesForHandler | bool | false | Since 1.3.2. Your game draws in-app messages with its own handler: hold messages that are ready before SetInAppMessageHandler is called. See Before your handler is set. |
Debug sets both the native log level and the native debug mode on iOS and
Android, which also unlocks the testing calls ResetDisplayedCampaigns and
EvaluateInAppCampaigns.
Push notifications
If your game has no push stack yet, the SDK registers the device itself - on iOS always, and on Android when Firebase Cloud Messaging is in the game (see Requirements). Without Firebase on Android there is no FCM token, so nothing is registered and push stays off; analytics and in-app messaging are unaffected. Ask for the permission when the player has a reason to say yes - a prompt in the first seconds of a game is the one most often denied, and a denial is much harder to reverse than a delay.
JoryioSDK.RequestPushPermission(granted => {
Debug.Log(granted ? "Push enabled" : "Player declined");
});
The callback runs on the main thread, exactly once. On iOS the SDK then receives the APNs device token by itself - there is no app delegate to edit.
On Android the prompt is shown from your game's activity: GameActivity (the
Unity 6 default) or the classic UnityPlayerActivity (earlier versions, or a
Unity 6 project that selects it). Both report the player's answer once the
system dialog closes. Below Android 13 there is no runtime prompt, and the
callback answers at once with whether notifications are enabled.
If you would rather the SDK ask during Initialize - a soft-launch build, a
title where notifications are the point, or just to see the flow - turn it on in
the config:
JoryioSDK.Initialize(sdkKey, "api-eu1.joryio.com", new JoryioConfig {
RequestPushPermissionAtLaunch = true,
});
It is off by default rather than unavailable. The prompt is one-shot on both platforms, so the default protects the common case; this is here for when you have decided otherwise.
If you already use Firebase or Unity Mobile Notifications
Most games do, and two push stacks competing for the same token is the usual way an integration breaks. Keep your existing one and hand us the token - the APNs device token on iOS, the FCM registration token on Android. On Android that token comes from Firebase Messaging (or your own FCM wrapper): Unity Mobile Notifications does not produce an FCM token on Android - it schedules local notifications there - so it can supply the token on iOS only.
JoryioSDK.RegisterPushToken(tokenFromYourExistingStack);
Add a callback to hear how it went - isRetryable means a transient failure (offline, DNS, 5xx) worth registering again, isUnauthorized means the SDK key is wrong:
JoryioSDK.RegisterPushToken(tokenFromYourExistingStack, result =>
{
if (!result.success && result.isRetryable)
{
// transient (offline, DNS, 5xx): register again when the network is back
}
});
On Android the SDK then never touches the FCM service. On iOS two things still happen, so nothing surprises you:
Initializemakes the SDK theUNUserNotificationCenterdelegate and forwards every callback to the delegate that was installed before it. Initialize Joryio after your other push plugin sets its delegate (Unity Mobile Notifications does so at launch, soAwakeis fine). A plugin that sets the delegate later replaces Joryio's, and open tracking then depends on yourTrackPushClickcall.- The SDK receives the APNs token from Unity's app controller itself. Passing
the same token to
RegisterPushTokenas well is harmless - unless the player has turned push off, becauseRegisterPushTokenlifts anUnregisterPush()(see below).
RegisterPushToken turns push back on for this device: it lifts an earlier UnregisterPush(). Firebase's OnTokenReceived fires by itself at every start, so if you forward it, skip it while the player has turned push off in your settings, and call RegisterPushToken when they turn it back on. Otherwise a token refresh, not the player, undoes their choice.
UpdateBadgeCount(count) and ClearBadge() set the app icon badge on iOS. They
do nothing on Android, which has no badge API of its own.
In-app messages
The dependency manifest pulls the UI-free native SDKs, so out of the box nothing draws in-app messages: until you install a handler or add the native UI, campaigns reach the device and are not shown. Choose one of the two.
Render them in-engine with your own art (recommended for games) - install a handler:
JoryioSDK.SetInAppMessageHandler(message => {
// Title / Body / ImageUrl / Buttons are the authored content.
// Name is the campaign's dashboard label - never copy to show a player.
myCanvas.Show(message.Title, message.Body, message.ImageUrl);
foreach (var button in message.Buttons)
myCanvas.AddButton(button.Text, () => {
message.MarkClicked(button);
if (button.Action == "url" || button.Action == "deep_link")
Application.OpenURL(button.Url);
else if (button.Action == "request_push_permission")
JoryioSDK.RequestPushPermission(granted => { });
myCanvas.Hide();
message.MarkDismissed(); // the tap closed the message
});
message.MarkDisplayed();
}, canRenderHtml: false);
Installing a handler stops the SDK drawing, and from that point it cannot see
what happened. Call MarkDisplayed() when the message appears,
MarkClicked(button) when a button is tapped and MarkDismissed() when the
message closes - a tap that closes it is reported as both, clicked then
dismissed, as the native SDKs do when they draw it. MarkDisplayed()
reports once per message however often it is called, and MarkClicked(button)
sends the button's Id as the click's elementId so the click map can tell
buttons apart. A button without an id sends no elementId - never its label,
which is display copy.
A message that is never reported counts as never shown: no display in reporting, nothing for revenue attribution to join on, and frequency caps that never count it. The campaign then reads as delivered to nobody, which looks like a targeting problem rather than an integration one. The SDK logs a warning if a message is discarded unreported.
To hand rendering back to the native SDK - when your renderer is destroyed, or a scene has no UI to draw on - remove the handler:
JoryioSDK.ClearInAppMessageHandler(); // the same as SetInAppMessageHandler(null)
The native SDK then draws messages with its own views again (if its UI
artifacts are in the build - see below) and reports its own
capabilities to the server. This is the native SDKs'
clearInAppMessageCallback.
From a component that may not be the only renderer - a renderer per scene, say - pass the handler you installed. It is removed only if it is still the installed one, never a handler another script installed after it:
void OnDestroy() => JoryioSDK.ClearInAppMessageHandler(myHandler);
canRenderHtml describes your renderer. A plain Unity canvas cannot display
HTML content, which needs a web view. This is not cosmetic: campaigns are
targeted on what the device reports, and an undeclared capability is read as
"cannot", not "unknown". Claiming HTML you cannot draw sends campaigns to
players who will never see them; omitting HTML you can draw withholds campaigns
you could have shown.
If your web view renders an HTML message with a <form data-jry-form>, submit
it through the SDK so the values are written to this player's profile. Values
are strings, or a list of strings for a checkbox group:
JoryioSDK.SubmitInAppForm(message.Id, new Dictionary<string, object> {
{ "email", "[email protected]" },
{ "topics", new List<string> { "events", "offers" } },
}, result => {
if (!result.ok) Debug.Log(result.message);
});
The callback always runs once; any failure, including no network, is
ok = false.
Before your handler is set
Since 1.3.2. SetInAppMessageHandler can only be called after Initialize, and the first sync can finish before it - the native SDK, if its UI is in the build, then draws that message itself. Set HoldInAppMessagesForHandler = true in JoryioConfig to prevent that: a message that is ready before your handler is set is held - not drawn, not reported, nothing counted against a frequency cap - and handed to your handler the moment you set it. ClearInAppMessageHandler() instead ends the hold and the native SDK draws the held message.
Or let the native SDK draw them
To have the native SDK draw messages itself - an OS-styled overlay over your game, reported for you, no code - add its UI artifacts.
Android: add the UI artifact in an EDM4U dependencies file of your own, for
example Assets/Editor/JoryioUIDependencies.xml, keeping the version equal to
the package's:
<dependencies>
<androidPackages>
<androidPackage spec="io.joryio:joryio-android-ui:1.3.3" />
</androidPackages>
</dependencies>
iOS: add the scripting define symbol JORYIO_IOS_UI under Player
Settings → iOS → Other Settings → Scripting Define Symbols. The iOS build then
links the Swift package's JoryioUI product as well, at the package's version.
A handler installed with SetInAppMessageHandler still takes over from the
native views.
What a message carries
The handler receives everything the server sent, under the same field names the
React Native SDK uses (it used to receive only Id and Name, which left a game
nothing to draw but the campaign's internal label):
| Field | Set when | Meaning |
|---|---|---|
Id, Name, Type | always | Campaign id, dashboard name (not copy to show), and modal / banner / slideup / fullscreen / custom. |
Kind | always | native or html - which of the groups below is populated. IsNative / IsHtml are shortcuts. |
Title, Body, ImageUrl | native | The authored text and image. Title and ImageUrl may be null; Body is always set. |
Buttons | native | Never null. Each has Id, Text, Action (dismiss, url, deep_link, request_push_permission) and Url for the two link actions. The SDK cannot act on a button you draw: open the URL yourself, call RequestPushPermission for the primer action, and report the tap with MarkClicked(button). |
CloseButton, BackdropDismissible | native | Whether the author asked for a close affordance and whether tapping outside should dismiss. |
Style | native | The author's presentation overrides, or null when the campaign set none. BackgroundColor, TextColor, PrimaryButtonColor, PrimaryButtonTextColor (CSS colour strings), CornerRadius, FontSize (body size - scale the headline from it), TitleWeight (regular / medium / semibold / bold), TextAlign (auto / start / center / end - auto follows the message's own language, not the device locale) and FontFamily. Every one is optional and an absent one means inherit your game's own styling; apply the ones that are present and do not substitute defaults for the rest. FontFamily is best effort - apply it only if your game ships that font. Same fields and names as the React Native SDK's message.style. |
Html, Css | html | The document. A uGUI canvas cannot render it - only declare canRenderHtml: true if your game hosts a web view. |
What Unity cannot render, stated plainly. HTML content needs a web view the
engine does not provide. Images are URLs: download them yourself
(UnityWebRequestTexture). The author's style overrides arrive in Style, but
nothing applies them for you - a canvas has no theme to inherit from, so your
renderer reads the fields it supports and keeps its own styling for the rest.
Start from the sample
The package ships an In-app renderer sample: a scene, a restylable prefab
and the script that wires them together. Import it from the Package Manager
(select the Joryio package, open Samples, click Import), put your SDK keys
on the JoryioInApp object in JoryioInAppSample.unity, and build to an iOS
or Android device. Messages arrive only in a device build: in the Editor the
SDK is a no-op, so pressing Play shows nothing.
Installed from the .unitypackage? There is no Package Manager entry to import
from: download joryio-unity-<version>-sample.unitypackage from the same
releases page and import it the same way. It lands in
Assets/Samples/Joryio/<version>/In-app renderer, where the Package Manager
puts the sample, and needs com.unity.ugui as well.
The prefab is the part to make yours - swap the colours, the font, drop in your
own art. The renderer draws whatever is in its Panel Prefab field
(JoryioInAppPanel.prefab in the sample scene) and fills in four children found
by name: Title and Body (Text), PrimaryButton (copied once per campaign
button) and SecondaryButton (the close button). Author style overrides are
applied on top where the campaign set them. With the field empty it builds a
plain panel in code.
uGUI buttons need an EventSystem, so the renderer creates one when the scene has
none - with the Input System's UI module when that is your active input
handling, otherwise the legacy one. The script shows the one thing to keep:
reporting each message's lifecycle. It requires com.unity.ugui, which the SDK
itself does not.
When a message counts as displayed
A message counts as displayed - in_app.displayed is reported and the campaign's frequency cap is spent - only once the person could have seen it. The time it must be on screen is IN_APP_DISPLAY_DWELL_MS (1000 ms), the same constant on every Joryio SDK.
- Messages the native SDK draws follow the iOS and Android rule: shown only on a settled screen in the foreground, and counted after 1 second on screen with the app in the foreground. A tap or a close before then counts as seen; a message that leaves the screen sooner is not counted and stays eligible.
- If the game draws messages itself, call
MarkDisplayed()by the same rule -JoryioSDK.IN_APP_DISPLAY_DWELL_MS: once the message has been visible that long, or right beforeMarkClicked/MarkDismissedwhen the player acts sooner. Do not call it for a message that left the screen first (a scene change, the app paused).
Consent
Two independent switches, the same on every Joryio SDK:
- Tracking consent - your consent screen's answer:
Granted,DeniedorPending. - Opt-out - the player's own choice:
OptOut()/OptIn().
Data is collected only when consent is granted and the player has not opted
out; either "no" wins. Neither switch changes the other:
UpdateConsent(TrackingConsent.Granted) does not lift an opt-out (only
OptIn() does), and OptOut() does not change the consent.
If the player has not answered your consent screen yet, start with consent
Pending: nothing is sent - no session start, no in-app sync, no push
registration - and events wait on the device (up to MaxQueueSize) until the
answer arrives.
JoryioSDK.Initialize(sdkKey, apiHost, new JoryioConfig {
TrackingConsent = TrackingConsent.Pending,
});
// ...once the player answers
JoryioSDK.UpdateConsent(TrackingConsent.Granted); // sends what was queued
// or
JoryioSDK.UpdateConsent(TrackingConsent.Denied); // drops it, collects nothing
Deniedis remembered across restarts. It is lifted only byUpdateConsent(TrackingConsent.Granted)or by an explicitTrackingConsent = TrackingConsent.Grantedin the config.- Leaving
JoryioConfig.TrackingConsentunset (the default) keeps a stored denial; with nothing stored the SDK tracks normally, so a game without a consent screen needs no setting. Pendingapplies to the current launch only and never overrides a storedDenied.- A value outside the enum (a bad cast) is ignored with a warning; in the config it is treated as not set.
JoryioSDK.OptOut(); // stop collecting; queued events and pending attribute writes are discarded
JoryioSDK.OptIn(); // resume
JoryioSDK.WipeData(); // erase everything stored on the device (the opt-out is kept)
An opt-out is stored and survives restarts. To start a session already opted
out, set JoryioConfig.OptOut = true.
IsUserOptedOut() reflects the opt-out switch only, not the consent.
GetTrackingConsent() returns the consent switch: Granted, Denied or
Pending. Both answer before Initialize from what is stored on the device:
an opt-out or a Denied remembered from an earlier launch is reported as such,
not as a default, and an OptOut() / OptIn() made earlier in the same launch
wins over the stored opt-out. GetTrackingConsent() returns Granted only when
nothing has been decided, which is also how the SDK behaves then.
When the opt-out changes, the SDK records it on the player's profile as the
boolean attribute tracking_opted_out, so segments can exclude opted-out
players. GetTrackingConsent() reports the consent the native SDK holds,
including a Denied remembered from an earlier launch.
SDK authentication
The token proves who an identified user is: requests that name a user ID must carry a valid one. Anonymous visitors have no token to show, so their requests are accepted without it, unless the workspace also turns on strict identity.
For workspaces with SDK authentication turned on, every request carries a token your server signs:
JoryioSDK.Initialize(sdkKey, apiHost, new JoryioConfig { EnableSdkAuthentication = true });
JoryioSDK.SetSdkAuthenticationToken(tokenFromYourServer); // after login
JoryioSDK.SetSdkAuthenticationErrorHandler(error => {
// error.reason: missing, invalid, expired, sub_mismatch or unknown
// error.refreshExhausted: the SDK has stopped retrying this request
FetchFreshToken(token => JoryioSDK.SetSdkAuthenticationToken(token));
});
SetSdkAuthenticationToken and SetSdkAuthenticationErrorHandler work before Initialize too. The native SDK starts with that token, ahead of its first request, and keeps it through a WipeData() made before Initialize, so an unsubscribe held for the player being erased still carries their token. You can also pass the first token as JoryioConfig.SdkAuthenticationToken.
The handler runs on the main thread. It is called for an authentication 401
on the current player's requests, including before any token was set (reason
missing) - never for the leftovers of a player who has logged out, so it never
makes you sign out the player who is playing now. The current player's refused
events and writes are kept and retried once you set a fresh token. Events go out in batches of one player at a time, and events
and writes of a player who has logged out are dropped on any authentication
401 (missing, invalid, expired or sub_mismatch), so they never hold
back the next player's. Reset() takes the token out of the session; it stays with the outgoing player's leftovers, and the next player's requests never carry it - set their token with SetSdkAuthenticationToken once they sign in. Events and writes the outgoing player had already queued still go out with that player's token, so a normal logout loses nothing.
E-commerce
Use the typed tracker rather than Track for commerce. It applies the canonical
event names that revenue reporting and attribution read; a hand-typed name such
as "Added to Cart" is stored as a different event and matches no report.
var item = new EcommerceCartItem { ProductId = "gems_500", Name = "500 gems", Price = 4.99, Quantity = 1 };
JoryioSDK.Ecommerce.AddToCart(item, cartValue: 4.99);
JoryioSDK.Ecommerce.Purchase(new EcommerceOrder {
OrderId = "order-1042",
Items = new List<EcommerceCartItem> { item },
Value = 4.99,
Currency = "USD",
});
The tracker has the same twenty events as the React Native SDK, from
ViewProduct to SubmitReview.
A call missing a required argument logs a warning and sends nothing - never an
empty or zero event: a product without a ProductId, an order without an
OrderId or Items, an empty order or coupon id, a cart call without its item
list. An amount that is NaN or infinite (Value, Price, cartValue,
Shipping, Tax, Discount, refundAmount, discountAmount) is refused the
same way, rather than recorded as 0.
Identity and session
Always send phone numbers in full international format (E.164): +, the country code, then the number - for example +972501234567. A number without + is read using the workspace's default country for phone numbers; if none is set, or the digits can be read two ways, it is saved as unverified and not used for messages. How phone numbers are handled
var (userId, anonymousId) = JoryioSDK.GetIdentity(); // userId is null when anonymous
var (sessionId, sessionStart, duration) = JoryioSDK.GetSessionInfo(); // duration in seconds
Reset() logs the player out: a new anonymous id, no user id.
API reference
Every public member of JoryioSDK, with its parameter names. All are static and
safe to call before Initialize (they log and do nothing, and a callback still
answers once), except the consent calls, WipeData() and the SDK authentication calls, which take effect, and
the subscription calls and UnregisterPush(), which are held until it.
| Method | What it does |
|---|---|
Initialize(sdkKey, apiHost, config = null) | Start the SDK. A second call is ignored. A key for another platform (iOS keys start with jry_sdk_ios_, Android keys with jry_sdk_android_) is refused with an error and the SDK stays off. See Configuration. |
IsInitialized | Whether Initialize succeeded. True on an iOS launch before the device's first unlock too: the native SDK finishes starting at the unlock, and calls made meanwhile wait for it. If calls held by an earlier launch are still on the device, iOS keeps them unreadable until that unlock too: the SDK collects nothing and every call waits until they can be read, then sends them first and the calls made since after them, in order. Meanwhile GetTrackingConsent() and IsUserOptedOut() report the decisions this launch made (calls and JoryioConfig). Without one, GetTrackingConsent() reports Pending, because the stored consent cannot be read yet, and IsUserOptedOut() answers from the native SDK, which may not see an opt-out stored by an earlier launch until the unlock. When the calls are sent, the consent applied is the one this launch set explicitly (UpdateConsent before Initialize, else JoryioConfig.TrackingConsent); without one, the decision saved on the device applies, read after the unlock, the same as the native SDKs: a stored Denied stays Denied, and with none stored the SDK is Granted, as on any other launch. Other getters, such as GetUserId(), GetAnonymousId() and GetSessionId(), still answer from the native SDK and do not yet reflect the calls that are waiting. |
SdkVersion | The SDK version, "1.3.3". |
Track(eventName, properties = null) | Track an event. |
TrackScreen(screenName, properties = null) | Track a screen view. |
Flush() | Send queued events now. |
Identify(userId) | Identify the player. While the player is opted out or consent is Denied, the id is only remembered on the device (nothing is sent), so an unsubscribe made meanwhile reaches the right player. |
Reset() | Log out: a new anonymous id, no user id. |
AddAlias(label, name) | Attach another identifier, such as ("steam_id", "7656..."). Works for an anonymous player. |
GetUserId() / GetAnonymousId() | The current ids ("" when absent). |
GetIdentity() | (userId, anonymousId); userId is null when anonymous. |
SetAttributes(attributes) | Set several attributes. A null value unsets that attribute. |
SetAttribute(key, value) | Set one attribute, keeping its type. A null value unsets it; NaN or infinity is ignored. |
IncrementAttribute(key, by = 1) | Add to a numeric attribute. NaN or infinity is ignored. |
UnsetAttribute(key) | Remove an attribute. |
UpdateConsent(consent) | TrackingConsent.Granted, Denied or Pending. Never changes the opt-out. Works before Initialize. See Consent. |
OptOut() / OptIn() | Stop or resume collecting. Stored across restarts; never changes the consent. Work before Initialize. |
IsUserOptedOut() | Whether the player is opted out (the opt-out switch only). Answers before Initialize too, from the stored decision. |
GetTrackingConsent() | The current consent: TrackingConsent.Granted, Denied or Pending (the consent switch only). Answers before Initialize too, from the stored decision. |
WipeData() | Erase everything stored on the device. The opt-out is kept. |
SetSubscription(channel, status) | Marketing subscription for one channel. See Marketing Subscription. |
AddToSubscriptionGroup(groupId, channel) / RemoveFromSubscriptionGroup(groupId, channel) | Join or leave a subscription group. |
RequestPushPermission(onResult) | Ask for the notification permission; onResult(bool granted). |
GetPushPermissionStatus(onResult) | "granted", "denied", "notDetermined" or "unavailable". |
IsPushEnabled(onResult) | Whether notifications are enabled; onResult(bool). |
RegisterPushToken(token, onResult = null) | Hand over a token from your own push stack; onResult(PushRegistrationResult). |
ReportPushDelivered(trackingId) | Report that a push arrived (your own push stack). |
TrackPushClick(trackingId) | Report that a notification was opened. |
UnregisterPush() | Stop push to this device. Stays off across restarts until RegisterPushToken (after Initialize) or a permission request. Held when called before Initialize. |
GetDeviceToken() | The push token the SDK holds ("" when none). |
UpdateBadgeCount(count) / ClearBadge() | iOS app icon badge; no-ops on Android. |
GetSessionId() | The current session id. |
GetSessionInfo() | (sessionId, sessionStart, duration); duration in seconds. |
SetSdkAuthenticationToken(token) | Supply a signed token. See SDK authentication. |
SetSdkAuthenticationErrorHandler(handler) | Hear when a request is rejected under SDK authentication; null stops listening. |
Ecommerce | The typed e-commerce tracker. See E-commerce. |
SetInAppMessageHandler(handler, canRenderHtml = false) | Render in-app messages yourself. Null hands rendering back to the native SDK. |
ClearInAppMessageHandler() | Hand in-app rendering back to the native SDK. |
ClearInAppMessageHandler(handler) | The same, only if handler is still the installed one. A null handler clears whatever is installed. |
TrackInAppImpression(campaignId, action, elementId = null) | Report displayed, clicked or dismissed; elementId is the tapped button's id. Prefer the message's Mark... methods. |
SubmitInAppForm(campaignId, values, onResult) | Submit a form from an HTML message you render; onResult(InAppFormSubmitResponse). |
SyncInAppCampaigns() | Fetch the latest campaigns now. |
EvaluateInAppCampaigns() / ResetDisplayedCampaigns() | Testing helpers; ignored unless Debug is on. |
On the message your handler receives: MarkDisplayed(), MarkClicked(button = null)
and MarkDismissed().
Building
The SDK configures the exported projects automatically. Nothing to do by hand:
- Xcode: the native SDK as a Swift package (
joryio-ios-sdk, linked intoUnityFramework), Swift version, Swift runtime embedding, the push capability, the remote-notification background mode, andUNITY_USES_REMOTE_NOTIFICATIONSinClasses/Preprocessor.h- without it Unity compiles out the callback that delivers the APNs token. The push capability is added to the target's existing entitlements file when it has one, so other plugins' entitlements are kept. - Android: keep rules for the Java bridge are appended to the generated
proguard-unity.txt, so a minified release build keeps the methods C# calls by name.
If notifications never arrive on a build that otherwise works, check that the Push Notifications capability is present on the target: it is the one setting whose absence produces no error anywhere.
Marketing Subscription
Whether this person may be messaged on a channel. A different consent from
OptOut() above, which is whether they may be observed - neither implies
the other, and someone can decline tracking and still want the newsletter.
This is what an in-app preference centre writes.
JoryioSDK.SetSubscription("email", "unsubscribed");
Channels: email, sms, whatsapp, push, viber.
Statuses: optedIn, subscribed, unsubscribed.
Subscription groups are lists a person can join or leave per channel:
JoryioSDK.AddToSubscriptionGroup("list_abc123", "email");
JoryioSDK.RemoveFromSubscriptionGroup("list_abc123", "email");
An identifier is required - the call is skipped, with a logged error, if there is neither a user id nor an anonymous id.