Players sign in on Wavedash before they launch your game. The injected SDK receives credentials from the host page and attaches them to each API call. You never embed login UI inside the game.
Reading the current player
func print_player():
var user = WavedashSDK.get_user()
var user_id = WavedashSDK.get_user_id()
var username = WavedashSDK.get_username()
print(user_id, " ", username, " ", user)void PrintPlayer()
{
var userId = Wavedash.SDK.GetUserId();
var user = Wavedash.SDK.GetUser();
Debug.Log($"{userId} {user?["username"]}");
}local user = wavedash.get_user()
local user_id = wavedash.get_user_id()
local username = wavedash.get_username()
print(user_id, username, user)const user = Wavedash.getUser();
const userId = Wavedash.getUserId();
const username = Wavedash.getUsername();
console.log(userId, username, user);| Method | Returns |
|---|---|
getUser() | Full profile object for the signed-in player |
getUserId() | Stable string ID |
getUsername() | Display name |
Looking up other players
getUsername(userId) returns the display name of any player your game has seen — a friend from listFriends(), or a lobby peer. It returns null for unknown users (an empty string in Godot), so call listFriends() once on startup to populate the cache.
var peer_name = WavedashSDK.get_username(peer_user_id)string name = Wavedash.SDK.GetUsername(peerUserId);local name = wavedash.get_username(peer_user_id)const name = Wavedash.getUsername(peerUserId);Gameplay tokens
Gameplay tokens are game-scoped and short-lived. Wavedash issues them when the session starts and the SDK attaches them to all API calls. When a token nears expiry, the SDK requests a fresh one. You do not read or store these tokens in normal game code.
Treat gameplay tokens as secrets at runtime. Do not log them or send them to your own servers unless you control the receiving endpoint.
Fetching the JWT explicitly
If you operate your own backend and want to authenticate the player using their Wavedash identity, call getUserJwt() to get a signed JWT you can forward to your server and verify there. The SDK returns a cached token when possible and refreshes it on demand.
func send_to_my_backend():
var response = await WavedashSDK.get_user_jwt()
if not response.success:
return
var jwt = response.data
# forward `jwt` to your own server in the Authorization headerasync void SendToMyBackend()
{
string jwt = await Wavedash.SDK.GetUserJwt();
if (string.IsNullOrEmpty(jwt))
return;
// forward `jwt` to your own server in the Authorization header
}local co = coroutine.create(function()
local response = wavedash.get_user_jwt_async()
if not response.success then
return
end
local jwt = response.data
-- forward `jwt` to your own server in the Authorization header
end)
assert(coroutine.resume(co))const response = await Wavedash.getUserJwt();
if (response.success) {
const jwt = response.data;
// forward `jwt` to your own server in the Authorization header
}Verify the JWT on your backend against the Wavedash public keys. The token is short-lived (1 hour); call getUserJwt() again if a previous token has expired.
Verifying the JWT on your backend
Tokens are signed with RS256 and the public keys are published at https://auth.wavedash.com/.well-known/jwks.json. Use any JWKS-aware library to verify — the following example uses jose in Node.js:
import { createRemoteJWKSet, jwtVerify } from "jose";
const JWKS = createRemoteJWKSet(
new URL("https://auth.wavedash.com/.well-known/jwks.json")
);
export async function verifyWavedashJwt(token: string) {
const { payload } = await jwtVerify(token, JWKS, {
issuer: "https://auth.wavedash.com",
audience: "gameplay.wavedash.com"
});
return payload;
}
async function getVerifiedWavedashUserId(jwt: string) {
const payload = await verifyWavedashJwt(jwt);
const userId = payload.sub;
return userId;
}A verified gameplay JWT payload looks like this (see UserJwtPayload in the types reference):
interface GameplayJwtPayload {
sub: string; // Wavedash user ID — use this as the player's identity
iss: "https://auth.wavedash.com";
aud: "gameplay.wavedash.com";
scope: "gameplay";
gbid: string; // game build ID
r2key?: string; // storage key of the uploaded build (uploaded builds only)
gcid: string; // game cloud ID (differentiates playtest from production)
ents: string[]; // content identifiers the player owns (what isEntitled reads)
iat: number; // issued-at (seconds since epoch)
nbf: number; // not-before (seconds since epoch)
exp: number; // expiry (seconds since epoch)
}Use payload.sub as the stable user ID for your own records. Reject the token if jwtVerify throws an error — it handles signature, issuer, audience, and expiry checks for you.
Wavedash loads your game in a cross-origin-isolated context, so fetch() calls to your backend need a passing CORS response (e.g. Access-Control-Allow-Origin: *), and resources loaded without CORS — images, audio, plain <script> tags — need Cross-Origin-Resource-Policy: cross-origin, or the browser will block them. WebSockets are unaffected — if you check the Origin header, just confirm the host ends with .builds.wavedashcdn.com.
Friends
The Friends API lets you retrieve the current player's friends list and display user avatars.
Listing friends
func _ready():
WavedashSDK.got_friends.connect(_on_got_friends)
func load_friends():
WavedashSDK.list_friends()
func _on_got_friends(response):
if response.get("success", false):
for friend in response.get("data", []):
print(friend["username"], " - Online: ", friend["isOnline"])async void Start()
{
var friends = await Wavedash.SDK.ListFriends();
if (friends != null)
{
foreach (var friend in friends)
Debug.Log($"{friend["username"]} - Online: {friend["isOnline"]}");
}
}local co = coroutine.create(function()
local response = wavedash.list_friends_async()
if response.success then
for _, friend in ipairs(response.data) do
print(friend.username, "- Online:", friend.isOnline)
end
end
end)
assert(coroutine.resume(co))const response = await Wavedash.listFriends();
if (response.success) {
response.data.forEach(friend => {
console.log(`${friend.username} - Online: ${friend.isOnline}`);
});
}Friend data
| Field | Type | Description |
|---|---|---|
userId | UserId | Friend's user ID |
username | string | Display name |
avatarUrl | string? | URL to avatar (if set) |
isOnline | boolean | Whether currently online |
User avatars
Get a CDN URL for a user's avatar. Users must be cached first — seen via the friends list, lobby membership, or the last leaderboard page you fetched.
| JS constant | Native size constant | Pixels | Use case |
|---|---|---|---|
Wavedash.AvatarSize.SMALL | WavedashSDK.Constants.AVATAR_SIZE_SMALL (Godot) / WavedashConstants.AvatarSize.SMALL (Unity) / wavedash.AVATAR_SIZE_SMALL (Defold) | 64px | Lists, compact UI |
Wavedash.AvatarSize.MEDIUM | WavedashSDK.Constants.AVATAR_SIZE_MEDIUM (Godot) / WavedashConstants.AvatarSize.MEDIUM (Unity) / wavedash.AVATAR_SIZE_MEDIUM (Defold) | 128px | Profile cards, chat |
Wavedash.AvatarSize.LARGE | WavedashSDK.Constants.AVATAR_SIZE_LARGE (Godot) / WavedashConstants.AvatarSize.LARGE (Unity) / wavedash.AVATAR_SIZE_LARGE (Defold) | 256px | Full profile view |
# Just the URL — cheap, synchronous
func avatar_url(user_id: String) -> String:
return WavedashSDK.get_user_avatar_url(user_id, WavedashSDK.Constants.AVATAR_SIZE_MEDIUM)
# Or: async helper that fetches + decodes to a Texture2D and emits a signal
func _ready():
WavedashSDK.user_avatar_loaded.connect(_on_avatar_loaded)
func load_friend_avatar(user_id: String):
WavedashSDK.get_user_avatar(user_id, WavedashSDK.Constants.AVATAR_SIZE_MEDIUM)
func _on_avatar_loaded(texture: Texture2D, user_id: String):
if texture:
$AvatarSprite.texture = texture// Just the URL — cheap, synchronous
string url = Wavedash.SDK.GetUserAvatarUrl(userId, WavedashConstants.AvatarSize.MEDIUM);
// Or: async helper that fetches + decodes to a Texture2D
public async void LoadFriendAvatar(string userId)
{
var texture = await Wavedash.SDK.GetUserAvatar(userId, WavedashConstants.AvatarSize.MEDIUM);
if (texture != null)
avatarImage.texture = texture;
}local url = wavedash.get_user_avatar_url(user_id, wavedash.AVATAR_SIZE_MEDIUM)
if url then
print("Avatar URL:", url)
endconst url = Wavedash.getUserAvatarUrl(userId, Wavedash.AvatarSize.MEDIUM);
if (url) {
const img = new Image();
img.src = url;
}If getUserAvatarUrl() / GetUserAvatarUrl() / get_user_avatar_url() returns null (an empty string in Godot), the user has not been cached yet or has no avatar set. Call listFriends() early to populate the cache.
Presence
The Presence API lets you share player status and activity with friends.
updateUserPresence takes a data object with two optional fields. Wavedash stores them with the player's presence; friends-list rows don't currently display them (they show "Playing …", "Playtesting", or "Online").
| Field | Description |
|---|---|
status | One-line activity (e.g. "Traveling in a group"). |
details | Secondary context (e.g. the current zone or game mode). |
await WavedashSDK.update_user_presence({
"status": "Traveling in a group",
"details": "Eastfield Forest"
})await Wavedash.SDK.UpdateUserPresence(new Dictionary<string, object> {
{ "status", "Traveling in a group" },
{ "details", "Eastfield Forest" }
});local co = coroutine.create(function()
local response = wavedash.update_user_presence_async(json.encode({
status = "Traveling in a group",
details = "Eastfield Forest",
}))
print("Presence updated:", response.success)
end)
assert(coroutine.resume(co))await Wavedash.updateUserPresence({
status: "Traveling in a group",
details: "Eastfield Forest"
});Clearing presence
Pass an empty dictionary or object to clear all active presence fields.
await WavedashSDK.update_user_presence({})await Wavedash.SDK.UpdateUserPresence(new Dictionary<string, object>());local co = coroutine.create(function()
wavedash.update_user_presence_async(json.encode({}))
end)
assert(coroutine.resume(co))await Wavedash.updateUserPresence({});