Created
May 28, 2026 13:56
-
-
Save DSKonstantin/cb075b1b4009e0f56a91a311392a4bb3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ============================================================================= | |
| // 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