Referrals
Referral links allow users to invite friends directly into your app, unlocking viral growth. For more information and best practices, see the Virality guide.
You can attach an entryPayload to carry attribution or customization into the invited player’s first session, and then track which invites converted (joined your app).
Conversions are grouped by reference, a stable campaign key you define (for example unlock_party_mode_v1 or tiktok_campaign_feb).
Attribution happens the first time a user enters your app. A user who has previously entered your app is not counted as a conversion if they later open a referral link.
Share a referral link
Open the platform share dialog with your provided title/text and a link to your app.
Sharing only opens the share dialog. It does not guarantee the user completes the share.
The entryPayload is embedded into the shared link. When an invited user enters your app through that link, you can read the payload as described in Entry payload.
- HTML5
- Unity
JestSDK.referrals.shareReferralLink(options)
JestSDK.referrals.shareReferralLink({
// A stable campaign key you choose to label invites (for example a feature or campaign name).
reference: string;
// Optional metadata that is passed to the invited user when they enter the app
entryPayload?: Record<string, unknown>;
// Optional title/text for the share dialog (platform dependent)
shareTitle?: string;
shareText?: string;
// Optional onboarding game slug to route invited players through first
onboardingSlug?: string;
// Optional notification templates sent to the referrer when invited players convert.
// Each template activates at or above its minConversionCount; the platform picks the
// template with the highest matching threshold and a variant from within it.
notificationTemplates?: {
minConversionCount: number;
variants: {
body: string;
ctaText: string;
title?: string | null;
imageReference?: string | null;
}[];
}[];
// Optional base64 data URL of an image to use as the og:image when the
// referral link is opened. The platform decodes, hashes, dedups, hosts
// the bytes on its CDN, and stores the resulting URL on the referral.
// Accepted MIME: image/png, image/jpeg, image/webp, image/gif. Data URL ≤ 2 MB.
shareImage?: string;
}): Promise<{
// True if the user dismissed the share dialog.
canceled: boolean;
}>;
Here’s an example:
const { canceled } = await JestSDK.referrals.shareReferralLink({
reference: "unlock_party_mode_v1", // Unlock Party Mode after enough successful invites
entryPayload: {
feature: "party_mode",
// Optional: include referrer-specific context for the invited player to read on entry
// (for example, a chosen pet name, loadout, or a short invite code).
referrer: { name: "Ava", petName: "Pickles" },
},
shareTitle: "Come play this with me",
shareText: "Join my game. I want to unlock Party Mode with you.",
});
if (canceled) {
// The user dismissed the share dialog.
}
shareReferralLink can throw an INVALID_ARGUMENTS error if the provided options do not match the required schema.
Referrals.OpenReferralDialog(options)
In the Unity SDK the share dialog is opened with OpenReferralDialog, available on JestSDK.Instance.Referrals.
var referrals = JestSDK.Instance.Referrals;
await referrals.OpenReferralDialog(new Referrals.OpenDialogOptions
{
reference = "unlock_party_mode_v1",
shareTitle = "Come play this with me!",
shareText = "Join my game. I want to unlock Party Mode with you.",
entryPayload = new Dictionary<string, object>
{
{ "feature", "party_mode" },
{ "referrer", new Dictionary<string, object>
{
{ "name", "Ava" },
{ "petName", "Pickles" }
}
}
}
});
OpenDialogOptions properties
| Property | Type | Description |
|---|---|---|
reference | string | A stable campaign key you choose to label invites (required). |
entryPayload | Dictionary<string, object> | Optional metadata passed to the invited player. |
shareTitle | string | Optional title for the share dialog (platform dependent). |
shareText | string | Optional text for the share message. |
onboardingSlug | string | Optional onboarding game slug to route invited players through first. |
NotificationTemplates | List<ReferralNotificationTemplate> | Optional notification templates sent to the referrer when invited players convert. Each template activates at or above its MinConversionCount; the platform picks the template with the highest matching threshold and a variant from within it. |
shareImage | string | Optional base64 data URL used as the OG preview image on the referral landing page. See Personalized link previews. When omitted, the app's static share image is used. |
OpenReferralDialog throws an ArgumentException if reference is null or empty.
Personalized link previews
Pass a base64 data URL via shareImage to make that image the OG preview on the referral's landing page — so when someone forwards the link or pastes it into a messaging app, they see your image instead of the app's default share card. The platform hosts the bytes on its CDN; you don't need a hosting setup of your own. Identical bytes are deduplicated by content hash, so re-sharing the same image is free.
Typical use cases: a score card the game rendered after a play, a personalized invite generated from the referrer's name, or a current-leaderboard snapshot.
Constraints: image/png, image/jpeg, image/webp, or image/gif; the data URL must be at most 2 MB. Oversize, wrong-MIME, or non-decodable inputs are rejected. If shareImage is omitted, the referral link uses the app's static share image. Animated GIFs are hosted unmodified, so the preview stays animated in messaging apps that support it.
- HTML5
- Unity
Render the image to a canvas and pass canvas.toDataURL(...) as shareImage. Oversize, wrong-MIME, or non-decodable inputs reject with BAD_REQUEST.
const canvas = document.querySelector("canvas")!;
await JestSDK.referrals.shareReferralLink({
reference: "share_score_v1",
shareTitle: "I scored 9,001!",
shareText: "Can you beat my score?",
shareImage: canvas.toDataURL("image/png"),
});
Use JestUtils.SpriteToDataUrl(sprite) (or JestUtils.TextureToDataUrl(texture)) to build the data URL. They render the source into an uncompressed copy first, so GPU-compressed (crunched/DXT) or non–Read/Write textures encode correctly — calling Texture2D.EncodeToPNG() on such a texture directly throws an "unsupported texture format" error.
var referrals = JestSDK.Instance.Referrals;
await referrals.OpenReferralDialog(new Referrals.OpenDialogOptions
{
reference = "share_score_v1",
shareTitle = "I scored 9,001!",
shareText = "Can you beat my score?",
shareImage = JestUtils.SpriteToDataUrl(myShareSprite),
});
List referral conversions
Retrieve referral conversions for the current user, grouped by reference.
Conversions include only invited users who complete registration.
- HTML5
- Unity
JestSDK.referrals.listReferrals()
JestSDK.referrals.listReferrals(): Promise<{
referrals: {
[reference: string]: { playerId: string; joinedAt: string }[];
};
referralsSigned: string;
}>;
const { referrals, referralsSigned } = await JestSDK.referrals.listReferrals();
const partyModeConversions = referrals["unlock_party_mode_v1"] ?? [];
console.log("Party Mode conversions:", partyModeConversions.length);
// Recommended: send `referralsSigned` to your backend and verify it before granting rewards.
Referrals.ListReferrals()
var referrals = JestSDK.Instance.Referrals;
var listTask = referrals.ListReferrals();
await listTask;
if (listTask.IsCompleted)
{
var response = listTask.Result;
foreach (var referral in response.referrals)
{
Debug.Log($"Reference: {referral.reference}, Registrations: {referral.registrations.Count}");
}
// Use response.referralsSigned for server-side verification
}
Response structure
public class ListReferralsResponse
{
public List<ReferralInfo> referrals;
public string referralsSigned; // JWS for server verification
}
public class ReferralInfo
{
public string reference;
public List<ReferralRegistration> registrations; // Players who joined via this referral
}
public class ReferralRegistration
{
public string playerId; // The referred player's ID
public string joinedAt; // ISO datetime of when they joined
}
Server-side verification (recommended)
If you grant rewards for referrals, verify referralsSigned on your backend instead of trusting client-reported conversions. This is the same pattern used for signed player payloads in the Player module and signed purchase payloads in the Payments module.
referralsSigned is an HS256 JWS signed with your app’s shared secret. The payload has this shape:
type ReferralsSignedPayload = {
referrals: Record<string, Array<{ playerId: string; joinedAt: string }>>;
aud: string; // game id
sub: string; // referrer player id
};
// npm i jose
import { jwtVerify } from "jose";
export async function verifyReferralsSigned(token: string, gameId: string) {
const secretBase64 = process.env.JWS_SECRET!;
const secret = Buffer.from(secretBase64, "base64");
const { payload } = await jwtVerify(token, secret, {
algorithms: ["HS256"],
audience: gameId,
});
return payload as {
referrals: Record<string, Array<{ playerId: string; joinedAt: string }>>;
aud: string;
sub: string;
};
}
Example: Unlock a feature after 3 invites
Suppose you want to unlock a feature (for example, “Party Mode”) after a player gets 3 successful invites.
Use a dedicated reference for this feature gate, for example:
reference: "unlock_party_mode_v1"
There are two common ways to implement this:
- Client-only (not server verified): simplest, good for low-stakes UX (cosmetics, UI hints). This can be spoofed by a modified client.
- Server verified: recommended for anything that affects entitlements, currency, or competitive balance. The server verifies
referralsSignedbefore unlocking.
Client-only (not server verified)
- HTML5
- Unity
const { referrals } = await JestSDK.referrals.listReferrals();
const inviteCount = (referrals["unlock_party_mode_v1"] ?? []).length;
if (inviteCount >= 3) {
unlockPartyModeLocally();
} else {
showInviteProgress({ inviteCount, required: 3 });
}
var referrals = JestSDK.Instance.Referrals;
var listTask = referrals.ListReferrals();
await listTask;
var response = listTask.Result;
// Find the referral info for our feature gate
var partyModeReferral = response.referrals
.FirstOrDefault(r => r.reference == "unlock_party_mode_v1");
int inviteCount = partyModeReferral?.registrations?.Count ?? 0;
if (inviteCount >= 3)
{
UnlockPartyModeLocally();
}
else
{
ShowInviteProgress(inviteCount, required: 3);
}
Server verified (recommended)
Client: send referralsSigned to your backend (and authenticate the user using your normal mechanism).
- HTML5
- Unity
const { referralsSigned } = await JestSDK.referrals.listReferrals();
await fetch("/api/referrals/unlock-party-mode", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
referralsSigned,
}),
});
var referrals = JestSDK.Instance.Referrals;
var listTask = referrals.ListReferrals();
await listTask;
var response = listTask.Result;
// Send to your backend for verification
var unlockResult = await UnlockPartyModeServerSide(response.referralsSigned);
if (unlockResult.unlocked)
{
UnlockPartyModeLocally();
}
Server: verify the JWS, count conversions for the reference, then unlock the feature in persistent state.
import { jwtVerify } from "jose";
export async function unlockPartyModeHandler(req: {
body: { referralsSigned: string };
}) {
const secretBase64 = process.env.JWS_SECRET!;
const secret = Buffer.from(secretBase64, "base64");
const gameId = process.env.GAME_ID!;
const { payload } = await jwtVerify(req.body.referralsSigned, secret, {
algorithms: ["HS256"],
audience: gameId,
});
const referrals = payload.referrals as Record<
string,
Array<{ playerId: string; joinedAt: string }>
>;
const reference = "unlock_party_mode_v1"; // hardcode server-side; do not trust client input
const inviteCount = (referrals?.[reference] ?? []).length;
if (inviteCount < 3) {
return { unlocked: false, inviteCount };
}
// Persist the entitlement (idempotent write recommended).
await grantPartyModeToPlayer({ playerId: payload.sub as string });
return { unlocked: true, inviteCount };
}
Testing referrals
To run a full referral loop against an uploaded build, use two sandbox users — one as the referrer, one as the invitee. See Test referrals for the step-by-step recipe.