Skip to main content

Introduction

This tutorial shows how to embed the SavageTech gamification widget on your site and connect it to the API securely.

Widget avatar icon

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
Why this pattern?
Opponent (friendly reminder)

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: playerId and currency (optional playername)

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 });
}
Never expose x-client-secret in the browser
Opponent (security warning)

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&currency=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.

Widget power icon Currency icon

When a JWT expires, request fresh credentials from your backend and update the widget:

const refreshed = await fetch('/api/widget-credentials?playerId=player-123&currency=USD').then((r) => r.json());
await Savage.setCredentials({ vendorId: refreshed.vendorId, jwt: refreshed.jwt, pubsub: refreshed.pubsub });
Token refresh is part of a production-ready integration
Opponent (stability warning)

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-credentials as 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 zIndex isn’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-secret are correct (server-side).
  • Ensure your vendor is active and allowed to request tokens.

What’s next?