Introduction
This tutorial shows how to embed the SavageTech gamification widget on your site and connect it to the API securely.
What you’ll build
- A backend endpoint that exchanges your vendor credentials for widget tokens
- A frontend snippet that loads the widget script and calls
Savage.init() - A simple refresh strategy for expired tokens
Architecture (recommended)

It keeps x-client-secret on the server side (never in the browser) and gives your integrators one clean endpoint to call.
Prerequisites
- API base URL (example:
https://your-api.azurewebsites.net) - Vendor client credentials:
x-client-id,x-client-secret - Player identification:
playerIdandcurrency(optionalplayername)
Step 1 — Create a backend “credentials” endpoint
Your backend should call SavageTech’s token endpoint and return only what the browser needs.
SavageTech token endpoint
- Method:
POST - URL:
{apiBaseUrl}/api/v1/auth/websocket-tokens - Headers:
x-client-id,x-client-secret,Content-Type: application/json - Body:
{
"playerId": "your-player-id",
"currency": "USD",
"playername": "Optional display name"
}
Example: Node/Express-style backend route
import type { Request, Response } from 'express';
export async function getWidgetCredentials(req: Request, res: Response) {
const playerId = String(req.query.playerId || '');
const currency = String(req.query.currency || 'USD');
const playername = req.query.playername ? String(req.query.playername) : undefined;
if (!playerId) {
return res.status(400).json({ error: 'playerId is required' });
}
const apiBaseUrl = process.env.SAVAGE_API_BASE_URL!;
const clientId = process.env.SAVAGE_X_CLIENT_ID!;
const clientSecret = process.env.SAVAGE_X_CLIENT_SECRET!;
const tokenRes = await fetch(`${apiBaseUrl}/api/v1/auth/websocket-tokens`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-client-id': clientId,
'x-client-secret': clientSecret,
},
body: JSON.stringify({ playerId, currency, playername }),
});
if (!tokenRes.ok) {
const text = await tokenRes.text();
return res.status(tokenRes.status).send(text);
}
const data = (await tokenRes.json()) as {
jwt: string;
pubsub: { url: string; baseUrl: string; token: string };
vendorId: string;
playerId: string;
};
// Return only what the browser needs.
return res.json({ vendorId: data.vendorId, jwt: data.jwt, pubsub: data.pubsub });
}
x-client-secret in the browser
Your frontend must not call /api/v1/auth/websocket-tokens directly, because it requires your vendor secret.
Step 2 — Load the widget script
Include the proxy bundle on your page so Savage is available.
<script src="https://your-cdn-or-host/savage-widget.js"></script>
Step 3 — Initialize the widget in the browser
Fetch credentials from your backend and call Savage.init().
const credentials = await fetch('/api/widget-credentials?playerId=player-123¤cy=USD').then((r) => r.json());
await Savage.init({
credentials: {
vendorId: credentials.vendorId,
jwt: credentials.jwt,
pubsub: credentials.pubsub,
},
config: {
theme: 'DEFAULT',
zIndex: 9999,
language: 'en_US',
},
});
Optional: match your UI
You can adjust theme colors and styling via config.styles.
Step 4 — Handle token refresh (recommended)
When a JWT expires, request fresh credentials from your backend and update the widget:
const refreshed = await fetch('/api/widget-credentials?playerId=player-123¤cy=USD').then((r) => r.json());
await Savage.setCredentials({ vendorId: refreshed.vendorId, jwt: refreshed.jwt, pubsub: refreshed.pubsub });

If you don’t refresh credentials when the JWT expires, the widget can stop receiving live updates and queries may fail.
Recommended approach:
- Treat
/api/widget-credentialsas your single source of truth. - Refresh on expiry (or on a 401/403) and then call
Savage.setCredentials(...).
Step 5 — Destroy the widget (optional)
Savage.destroy();
Troubleshooting
The widget doesn’t appear
- Confirm the widget script is loaded (check browser console/network).
- Confirm your
zIndexisn’t being overridden by your site. - Ensure your backend returns valid
{ vendorId, jwt, pubsub }.
401/403 when requesting credentials
- Verify
x-client-id/x-client-secretare correct (server-side). - Ensure your vendor is active and allowed to request tokens.
What’s next?
- Use Bulk add players to pre-create players during onboarding.
- Use Assign rewards on-demand as an integration point from CRM/wallet systems.
- If you use External rewards, read External rewards.
- For gameplay experiences, see Show / hide the widget.
- To understand click-through safe areas, see Forbidden zones.