Unity quickstart
Add the in-game support chat to a Unity game: mobile, desktop and Steam, with mouse, touch and gamepad.
Requirements
- Unity 2022.3 LTS or Unity 6. No dependency besides uGUI; works in any render pipeline (the UI is built from code on its own overlay canvas, no prefab).
- iOS, Android, Windows, macOS, Linux (Steam included). WebGL works with polling instead of a WebSocket.
- Works with the legacy Input Manager, the Input System package, or both.
- An app key (
app_…) from the inbox: Settings → Apps.
Install the package
The package name is gg.inngame.sdk. In Unity, open Window → Package Manager → + → Add package from git URL and paste:
https://github.com/inngame/sdk-unity.git#0.2.2
Or add it to Packages/manifest.json:
{
"dependencies": {
"gg.inngame.sdk": "https://github.com/inngame/sdk-unity.git#0.2.2"
}
}
If you received the SDK as a folder, use Add package from disk and pick its package.json. The package includes a Basic sample (pause-menu Help button with an unread badge) you can import from the Package Manager.
Configure
The quickest way: Tools → Inngame → Settings, paste your app key (dashboard → your app → Install) and click Send test message: it shows up in your inbox. Then tick Auto initialize. The settings are stored in Assets/Resources/InngameSettings.
Or configure from code, once, in your first scene:
using InngameSDK;
using UnityEngine;
public class Boot : MonoBehaviour
{
void Awake()
{
Inngame.Configure("app_XXXXXXXX", new InngameOptions
{
PauseGameWhileOpen = true, // Time.timeScale = 0 while the chat is open
ScreenshotHotkey = KeyCode.F8, // desktop: capture + open chat
ShakeToReport = true, // mobile: shake to capture + open chat
});
// Or, if you used Tools > Inngame > Settings:
// Inngame.ConfigureFromSettings(o => o.PauseGameWhileOpen = true);
}
}
Call it from the main thread. Configure is cheap: the session starts on the next frame, and an Identify made right after rides along. Calling it again with the same key just updates the options. Colors, texts and the welcome message set in the dashboard (Settings → Appearance) override the local theme, so you can restyle the chat without a new build.
Open the chat
Put a Support or Help entry wherever it fits: pause menu, settings, a floating button. Make sure it's reachable with every input your game supports.
helpButton.onClick.AddListener(Inngame.OpenChat);
// Unread badge on your own button
Inngame.UnreadChanged += n =>
{
badge.SetActive(n > 0);
badgeText.text = n > 9 ? "9+" : n.ToString();
};
// Optional hooks
Inngame.ChatOpened += () => audio.Pause();
Inngame.ChatClosed += () => audio.UnPause();
Inngame.MessageReceived += msg => Debug.Log("New reply from the team");
On phones the chat is full screen; elsewhere it's a centered panel. Force either with InngameOptions.FullScreen.
Identify the player
Every device starts as an anonymous player, so the chat works before login. When you know who the player is, identify them. With a signed token (JWT) or Steam, their history follows them to any device where they log in; a plain id only labels this device's player. See Player identity for the details and the security model.
// Unverified: your account id (fine for most games)
await Inngame.Identify(player.Id, player.DisplayName, player.Email);
// Verified: a JWT signed by YOUR server with the app's identity secret
string jwt = await MyBackend.GetInngameToken();
await Inngame.IdentifyWithToken(jwt);
// Verified Steam identity (see below for the ticket helper)
await Inngame.IdentifySteam(GetSteamTicketHex);
All identity calls return Task<bool>. Add player attributes your team should see (plan, progress, server…) with SetMetadata: up to 50 keys; values are strings (≤ 500 characters), numbers, booleans, or null to delete a key.
await Inngame.SetMetadata(new Dictionary<string, object>
{
["plan"] = "premium",
["levelReached"] = 42,
["guild"] = null, // removes the key
});
Steam ticket helper
IdentifySteam takes a function that returns a Web API ticket for the identity "inngame", as a hex string. Connect Steam in the dashboard first so the server can verify it.
// Steamworks.NET
using Steamworks;
using System.Threading.Tasks;
static Callback<GetTicketForWebApiResponse_t> ticketCb;
static Task<string> GetSteamTicketHex()
{
var tcs = new TaskCompletionSource<string>();
ticketCb = Callback<GetTicketForWebApiResponse_t>.Create(r =>
{
if (r.m_eResult != EResult.k_EResultOK) { tcs.TrySetResult(null); return; }
var sb = new System.Text.StringBuilder();
for (int i = 0; i < r.m_cubTicket; i++) sb.Append(r.m_rgubTicket[i].ToString("x2"));
tcs.TrySetResult(sb.ToString());
});
SteamUser.GetAuthTicketForWebApi("inngame");
return tcs.Task;
}
// after SteamAPI.Init():
await Inngame.IdentifySteam(GetSteamTicketHex);
// Facepunch.Steamworks
await Inngame.IdentifySteam(async () =>
{
var ticket = await SteamUser.GetAuthTicketForWebApiAsync("inngame");
return ticket == null ? null : System.BitConverter.ToString(ticket.Data).Replace("-", "");
});
Context, logs and screenshots
Each player message automatically carries, collected at send time: the active scene and the last 10 scenes; app, Unity and SDK versions; platform, device model, OS, CPU, RAM, battery and network; GPU (name, vendor, API, memory); screen (resolution, DPI, refresh rate, orientation, safe area); quality level, target FPS, vSync, average and minimum FPS; memory and GC; system language and time zone; and the last 50 log lines, with stack traces for errors. Add your own keys:
// A value attached to every message from now on (null removes it)
Inngame.SetContext("level", currentLevel);
Inngame.SetContext("saveSlot", 2);
// Or compute values at send time
Inngame.ContextProvider = () => new Dictionary<string, object>
{
["coins"] = wallet.Coins,
["quest"] = quests.Current?.Id,
};
// What would be attached right now (debugging)
Debug.Log(Inngame.CurrentContext().Count);
Context is shown to your team, never to the player. It is capped at 64 KB per message (the oldest log lines are dropped first). Set LogLines = 0 to disable log capture.
Screenshots. The chat has a screenshot button. You can also capture from code, from a desktop hotkey (F8 by default) or by shaking the phone. The chat itself is never in the capture.
Inngame.CaptureScreenshot(); // capture, attach to the next message, open the chat
Inngame.CaptureScreenshot(openChat: false);
await Inngame.AttachImage(pngBytes, "image/png"); // an image you already have
Photos from the gallery need native code: implement IImagePicker and pass it as InngameOptions.ImagePicker; a gallery button then appears in the composer. Photos are moderated before they're stored, and the camera and gallery buttons disappear when the app turns photos off (Settings → Moderation).
class GalleryPicker : IImagePicker // e.g. with yasirkula's NativeGallery
{
public void PickImage(Action<byte[], string> done) =>
NativeGallery.GetImageFromGallery(path =>
done(path == null ? null : File.ReadAllBytes(path), "image/jpeg"));
}
Gamepad, Steam Deck and desktop
- Everything is reachable with a gamepad or arrows and Tab, with a focus outline while navigating. Y jumps to the text field. B / Esc closes the image viewer, leaves the text field, then closes the chat.
- If the scene has no
EventSystem, the SDK creates one while the chat is open (InputSystemUIInputModulewith the Input System,StandaloneInputModuleotherwise). UnlockCursorWhileOpen(on by default) shows and frees the cursor while the chat is open and restores it afterwards.- On iOS and Android the system keyboard opens automatically. For the Steam Deck in Big Picture (or consoles), give the SDK your on-screen keyboard:
// Steamworks.NET
options.OnScreenKeyboard = (current, done) =>
{
SteamUtils.ShowGamepadTextInput(EGamepadTextInputMode.k_EGamepadTextInputModeNormal,
EGamepadTextInputLineMode.k_EGamepadTextInputLineModeMultipleLines, "Message", 4000, current);
pendingKeyboardCallback = done; // in your GamepadTextInputDismissed_t callback:
// SteamUtils.GetEnteredGamepadTextInput(out var text, len); done(text);
};
Look and feel
Create a theme with Assets → Create → Inngame → Theme (or InngameTheme.CreateDark() / CreateLight()): colors, corner radius, font size, a legacy font or a TextMeshPro font asset, title, welcome texts and placeholder. The dashboard's Appearance settings override it at runtime and are cached for the next launch.
In the dashboard (Settings → Apps → your app → Appearance) you can make the chat look like your game without a build: upload a logo (PNG or JPEG, square, up to 512 KB; it replaces the initial in the chat header), pick six colors for the light and dark themes (accent, background, text, header, team bubble, player bubble), or start from a preset (Inngame, Dark fantasy, Cartoon, Sci-fi, Minimal) that fills both themes in one click. Header and bubble colors you leave empty follow the background and the accent; text on bubbles is picked for contrast. The preview shows the result as you go. The chat interface is localized in 25 languages (en, fr, de, es, it, pt, ru, ja, ko, zh, tr, pl, nl, sv, da, nb, fi, cs, uk, ar, hi, th, vi, id, ro). TextMeshPro is used when available; otherwise the legacy text component, whose OS font fallback handles CJK out of the box.
Events and automations
Send game events; automations you set up in the dashboard react to them (a message, an offer, a review request, or opening the chat).
await Inngame.Track("level_failed", new Dictionary<string, object>
{
["level"] = 9,
["attempts"] = 5,
});
// Let an automation open the chat right away if it wants to
await Inngame.Track("checkout_cancelled", opensChat: true);
Event names: letters, digits, _, . and -, up to 64 characters.
Cards: items, offers, screens, reviews
Your team and the AI can send interactive cards. Wire the ones you use:
// "screen" cards open a screen of your game (the chat closes first)
Inngame.RegisterScreen("shop", tab => ShopUI.Open(tab));
// "offer" cards: show your own store or paywall
Inngame.OnOffer = offerId => Paywall.Show(offerId);
// "restore" cards: restore purchases with your IAP library
Inngame.OnRestorePurchases = () => Iap.Restore();
// "item" cards: the player claimed a gift (each card can be claimed once)
Inngame.OnItemGranted = item =>
Debug.Log($"Claimed {item.Quantity} x {item.Name}");
OnItemGranted fires only after Inngame confirms the claim, so an item is never granted twice, even with two devices or a lost connection, and never after your team unsent it.
Grant items on your server. OnItemGranted is there to refresh your UI. To actually credit the player, listen to the item.claimed webhook on your backend, which can't be faked by a modified client.
Availability. Under the chat title, players see how fast you usually reply (“Usually replies in a few hours”, from your average first reply over 30 days) and, outside your office hours, when you are back, in their local time (“Away · back Mon 09:00”). Set both in inbox → Settings → Apps → General. Outside office hours, a player who writes also gets an automatic reply in their language, at most once every 12 hours per conversation; you can change its text or turn it off. Inngame.Config.Availability has the raw values if you want them in your own UI.
Announcements. During an outage or for a known bug, write a note in inbox → Settings → Apps → General: it shows at the top of the chat for every player, translated, until the time you choose. Nothing to wire.
Satisfaction. If you turn on Ask players to rate (inbox → Settings → Apps → Reviews), closing a conversation sends a rating card: the question in the player's language and five buttons, reachable with a gamepad. Nothing to wire. A 4 or 5 is followed by a review card; a 1 or 2 flags the conversation for your team. Ratings show up in Stats and in the conversation.rated webhook.
Review cards open the right store page. If the card doesn't carry the ids, the SDK uses AppStoreId, PlayStorePackage and SteamAppId from your options (or the app settings).
Push notifications
When the team answers and the chat isn't open, Inngame sends a push through APNs or FCM. Get the token with your usual plugin (Unity Mobile Notifications, Firebase Messaging) and pass it on. Connect APNs or FCM in the dashboard.
// iOS: com.unity.mobile.notifications
using Unity.Notifications.iOS;
using (var req = new AuthorizationRequest(AuthorizationOption.Alert | AuthorizationOption.Badge | AuthorizationOption.Sound, true))
{
while (!req.IsFinished) yield return null;
if (!string.IsNullOrEmpty(req.DeviceToken))
Inngame.SetPushToken(req.DeviceToken, Debug.isDebugBuild ? PushKind.ApnsSandbox : PushKind.Apns);
}
// Android: Firebase Cloud Messaging
Firebase.Messaging.FirebaseMessaging.TokenReceived += (_, e) => Inngame.SetPushToken(e.Token, PushKind.Fcm);
// Unregister
Inngame.SetPushToken(null, PushKind.Fcm);
Logout() also clears the token on the server.
Logout and data deletion
// The player logged out of your game: the device continues as a new anonymous player
await Inngame.Logout();
// Right to erasure: deletes the player, their conversations and photos on the server, then local data
await Inngame.DeleteData();
Put DeleteData behind a "Delete my support data" button in your privacy settings. What the SDK collects, and what to declare in the store privacy forms: Privacy & data.
API reference
Everything is on the static class InngameSDK.Inngame.
| Member | What it does |
|---|---|
Configure(appKey, options) | Starts the SDK. Safe to call again. |
ConfigureFromSettings(customize) | Starts from Resources/InngameSettings. Returns false if missing. |
Shutdown() | Closes the chat and the connection. |
IsConfigured | Whether the SDK is running. |
Identify(userId, name, email) | Unverified identity. |
IdentifyWithToken(jwt) | Verified identity (HS256 JWT). |
IdentifySteam(getTicketHex) | Verified Steam identity. |
SetMetadata(dict) | Player attributes for the team. |
Logout() / DeleteData() | New anonymous player / erase everything. |
CurrentUser | The current player as the server sees them. |
OpenChat() / CloseChat() / IsChatOpen | Show or hide the chat. |
UnreadCount | Unread team messages. |
IsUnavailable | true while your support is unavailable (your Inngame workspace is suspended). The chat then says “Support is unavailable right now. Please try again later.” in the player's language and hides the composer; unsent messages wait and are sent once support is back. The SDK checks again every 10 minutes while the chat is open and each time it opens, and returns to normal on its own. Meanwhile Identify, Track, Logout and DeleteData return false. |
Messages | Loaded messages (read-only). |
SendMessage(text) | Send as if the player typed it (with context and pending screenshot). |
Track(name, props, opensChat) | Game event for automations. |
SetContext(key, value) / ContextProvider | Custom context on each message. |
CaptureScreenshot(openChat) / AttachImage(bytes, mime) | Attach images to the next message. |
SetPushToken(token, kind) | Register for push (Apns, ApnsSandbox, Fcm). |
RegisterScreen(id, open) | Targets for "screen" cards. |
OnItemGranted, OnOffer, OnRestorePurchases | Card callbacks. |
UnreadChanged, ChatOpened, ChatClosed, MessageReceived | Events. |
Language | Language used for the UI and translation (system language unless set in options). |
InngameOptions
| Option | Default | Notes |
|---|---|---|
BaseUrl | hosted service | Your Inngame server. |
Language | system | BCP-47, e.g. "fr", "pt-BR". |
Theme | dark | An InngameTheme asset (colors, corner radius, font size). Dashboard appearance overrides it. |
PauseGameWhileOpen | false | Sets Time.timeScale to 0 while open. |
UnlockCursorWhileOpen | true | Shows and unlocks the cursor, then restores it. |
LogLines | 50 | Recent log lines per message. 0 disables. |
ContextProvider | — | Extra context computed at send time. |
ScreenshotHotkey | KeyCode.F8 | KeyCode.None disables. |
ShakeToReport | false | Shake the phone to capture and open the chat. |
ScreenshotQuality / ScreenshotMaxSize | 80 / 1600 | JPEG quality, longest side in pixels. |
ImagePicker | — | Your IImagePicker for the gallery. |
TokenStore | PlayerPrefs | Where the device token and caches live (ITokenStore). |
OnScreenKeyboard | — | Text entry hook for Steam Deck / consoles. |
UseWebSocket | true | Falls back to polling when unavailable (WebGL). |
PollIntervalClosed / PollIntervalOpen | 15 / 4 s | Polling when there's no socket. |
AppStoreId, PlayStorePackage, SteamAppId | — | For review cards. |
SortingOrder | 30000 | Canvas order, above your UI. |
FullScreen | auto | true / false to force a layout. |
LogLevel | Warning | SDK logging. |