Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save DSKonstantin/cb075b1b4009e0f56a91a311392a4bb3 to your computer and use it in GitHub Desktop.

Select an option

Save DSKonstantin/cb075b1b4009e0f56a91a311392a4bb3 to your computer and use it in GitHub Desktop.
// =============================================================================
// Repackz GamePlatform — POST /sessions example client (Node.js 18+)
// =============================================================================
//
// Self-contained reference for casino operators integrating with Repackz.
// Run it directly:
//
// node repackz-session-launch-example.js
//
// All values below are STAGING credentials shared in Slack — never reuse them
// in production. The same shape (HMAC-SHA256 over
// `<timestamp>.<METHOD> <fullpath>.<body>`) is also what Repackz uses for
// their outbound webhooks (get_session / debit / credit / refund) to your
// side, so the sign + verify helpers can be shared verbatim in both
// directions.
// =============================================================================
const crypto = require('crypto');
// ---- Staging credentials -----------------------------------------------------
const REPACKZ_API_BASE = 'https://staging-api.getcardbase.com';
const REPACKZ_API_KEY = 'REPLACE_API_KEY';
const REPACKZ_API_SECRET = 'REPLACE_API_SECRET';
// Fresh operator_token issued by your side — replace with a live one.
const OPERATOR_TOKEN = 'REPLACE_OPERATOR_TOKEN';
// Game (= pool_offer) id available on staging. 364 / 397 are pre-seeded.
const GAME_ID = 364;
// ---- HMAC helpers ------------------------------------------------------------
/**
* Build the two HMAC headers Repackz expects on every authenticated request.
*
* IMPORTANT: `rawBody` MUST be the exact byte-for-byte string sent in the
* HTTP body. If you stringify a JSON object, sign that string and send it
* verbatim — do not re-serialise it on the way out.
*
* @param {string} apiSecret - your operator api_secret
* @param {string} method - HTTP method, e.g. 'POST', 'GET', 'DELETE'
* @param {string} fullpath - request URI (path + query, no host),
* e.g. '/loot_machine/api/game_platform/v1/sessions'
* @param {string} rawBody - exact body bytes (use '' for GET/DELETE)
*/
function signRepackz(apiSecret, method, fullpath, rawBody) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const stringToSign = `${timestamp}.${method.toUpperCase()} ${fullpath}.${rawBody}`;
const digest = crypto
.createHmac('sha256', apiSecret)
.update(stringToSign)
.digest('hex');
return {
'X-Repackz-Timestamp': timestamp,
'X-Repackz-Signature': `sha256=${digest}`,
};
}
/**
* Verify an inbound Repackz webhook (get_session / debit / credit / refund).
* Use on your /get_session etc. endpoints to authenticate the request really
* came from Repackz and not anyone with a leaked api_key.
*
* Capture `rawBody` BEFORE your JSON middleware re-parses it — once Express
* has serialised the body again, byte equality is gone.
*/
function verifyRepackz(apiSecret, method, fullpath, rawBody, headerTimestamp, headerSignature) {
if (!headerTimestamp || !headerSignature) return false;
// Reject timestamps outside ±5 min — basic replay protection.
const skewSeconds = Math.abs(Math.floor(Date.now() / 1000) - parseInt(headerTimestamp, 10));
if (Number.isNaN(skewSeconds) || skewSeconds > 5 * 60) return false;
const stringToSign = `${headerTimestamp}.${method.toUpperCase()} ${fullpath}.${rawBody}`;
const expected = 'sha256=' + crypto
.createHmac('sha256', apiSecret)
.update(stringToSign)
.digest('hex');
// Constant-time compare to thwart timing attacks.
const a = Buffer.from(headerSignature);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// ---- POST /sessions flow -----------------------------------------------------
async function launchSession() {
const path = '/loot_machine/api/game_platform/v1/sessions';
const url = `${REPACKZ_API_BASE}${path}`;
// 1. Stringify the body ONCE. Use this exact string for both signing
// and sending. Re-serialising elsewhere changes byte order/spacing
// and breaks signature verification on the server.
const rawBody = JSON.stringify({
game_id: GAME_ID,
token: OPERATOR_TOKEN,
platform: 'desktop',
language: 'en',
lobby_url: 'https://myprize.us',
deposit_url: 'https://myprize.us',
context: {
currency: 'SC',
username: 'mangoFish29',
country: 'US',
},
});
// 2. Sign.
const signedHeaders = signRepackz(REPACKZ_API_SECRET, 'POST', path, rawBody);
console.log('=== Request ===');
console.log('POST', url);
console.log('Authorization: Bearer', REPACKZ_API_KEY);
console.log('X-Repackz-Timestamp:', signedHeaders['X-Repackz-Timestamp']);
console.log('X-Repackz-Signature:', signedHeaders['X-Repackz-Signature']);
console.log('Body:', rawBody);
console.log();
// 3. Send. Note `body: rawBody` — the exact string we signed.
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${REPACKZ_API_KEY}`,
'Content-Type': 'application/json',
...signedHeaders,
},
body: rawBody,
});
const responseText = await response.text();
console.log('=== Response ===');
console.log('Status:', response.status);
console.log('Body: ', responseText);
if (!response.ok) {
throw new Error(`Repackz /sessions returned ${response.status}`);
}
const data = JSON.parse(responseText);
return data.result; // { session_token, launch_url, expires_at }
}
// ---- Entry point -------------------------------------------------------------
launchSession()
.then(result => {
console.log();
console.log('=== Launched ===');
console.log(JSON.stringify(result, null, 2));
})
.catch(err => {
console.error();
console.error('Failed:', err.message);
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment