SDK
The browser SDK is a thin convenience over the contracts in these docs — it mounts and talks to the iframe so you don’t hand-roll the postMessage protocol. For the S2S API, there’s no package to install: signing is a small HMAC envelope, so copy the recipe for your language below.
Browser SDK
Mounts the iframe, manages the postMessage protocol, and exposes typed events and commands.
<div id="games"></div><script type="module"> import { LBS } from 'https://{operator}.app.lootboxsolutions.com/sdk/games-core.js';
const games = LBS.mount('#games', { operator: 'https://{operator}.app.lootboxsolutions.com', launchUrl, // from your backend's /launches call parentOrigin: location.origin, locale: 'en', currency: 'EUR', });
games.on('walletBalanceChanged', ({ balanceMinor, currency }) => updateBalance(balanceMinor, currency)); games.on('authRequired', ({ boxId }) => promptSignIn(boxId)); games.on('navigationChanged', (loc) => syncUrl(loc)); // for refresh/deep-link
// recommended widget → navigate the parent to the box page games.on('boxCardClicked', ({ boxId }) => location.assign(`/games/box/${boxId}`)); games.on('boxCardPurchase', ({ boxId }) => location.assign(`/games/box/${boxId}?autoplay=1`));
// live, no reload: document.querySelector('#lang').onchange = (e) => games.setLocale(e.target.value); document.querySelector('#ccy').onchange = (e) => games.setCurrency(e.target.value); games.navigate({ boxId: 42 });</script>The SDK wraps:
- mounting / re-mounting the iframe and passing
parent,locale,currency; - all iframe → host events as
.on(...)handlers; - all host → iframe commands as methods
(
setLocale,setCurrency,navigate,setTheme,authenticate); - URL sync helpers for refresh & deep-linking.
Backend signing
There’s no server package — signing is small enough to own. Build the canonical string and HMAC it with your secret:
canonical = "{timestamp}\n{METHOD}\n{path}\n{sha256_hex(body)}"signature = hex(hmac_sha256(secret, canonical))Then send three headers — X-Key-Id, X-Timestamp, X-Signature (money
operations add Idempotency-Key). Here’s that recipe, returning the headers to
attach to an S2S request:
import { createHmac, createHash } from 'node:crypto';
function signHeaders({ keyId, secret, method, path, body = '' }) { const ts = Math.floor(Date.now() / 1000).toString(); const bodyHash = createHash('sha256').update(body).digest('hex'); const canonical = `${ts}\n${method.toUpperCase()}\n${path}\n${bodyHash}`; const signature = createHmac('sha256', secret).update(canonical).digest('hex'); return { 'X-Key-Id': keyId, 'X-Timestamp': ts, 'X-Signature': signature };}import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "strconv" "strings" "time")
func SignHeaders(keyID, secret, method, path, body string) map[string]string { ts := strconv.FormatInt(time.Now().Unix(), 10) sum := sha256.Sum256([]byte(body)) canonical := fmt.Sprintf("%s\n%s\n%s\n%s", ts, strings.ToUpper(method), path, hex.EncodeToString(sum[:])) mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(canonical)) sig := hex.EncodeToString(mac.Sum(nil)) return map[string]string{"X-Key-Id": keyID, "X-Timestamp": ts, "X-Signature": sig}}function sign_headers(string $keyId, string $secret, string $method, string $path, string $body = ''): array { $ts = (string) time(); $bodyHash = hash('sha256', $body); $canonical = "{$ts}\n" . strtoupper($method) . "\n{$path}\n{$bodyHash}"; $signature = hash_hmac('sha256', $canonical, $secret); return ['X-Key-Id' => $keyId, 'X-Timestamp' => $ts, 'X-Signature' => $signature];}import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec;import java.nio.charset.StandardCharsets;import java.security.MessageDigest;import java.time.Instant;import java.util.HexFormat;import java.util.Map;
static Map<String, String> signHeaders(String keyId, String secret, String method, String path, String body) throws Exception { String ts = Long.toString(Instant.now().getEpochSecond()); byte[] digest = MessageDigest.getInstance("SHA-256").digest(body.getBytes(StandardCharsets.UTF_8)); String bodyHash = HexFormat.of().formatHex(digest); String canonical = ts + "\n" + method.toUpperCase() + "\n" + path + "\n" + bodyHash; Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); String signature = HexFormat.of().formatHex(mac.doFinal(canonical.getBytes(StandardCharsets.UTF_8))); return Map.of("X-Key-Id", keyId, "X-Timestamp", ts, "X-Signature", signature);}using System.Security.Cryptography;using System.Text;
static Dictionary<string, string> SignHeaders(string keyId, string secret, string method, string path, string body = "") { var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); var bodyHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(body))).ToLowerInvariant(); var canonical = $"{ts}\n{method.ToUpperInvariant()}\n{path}\n{bodyHash}"; using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); var signature = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(canonical))).ToLowerInvariant(); return new() { ["X-Key-Id"] = keyId, ["X-Timestamp"] = ts, ["X-Signature"] = signature };}Verifying inbound calls
Your wallet RPC and
webhook receiver get the same
envelope from us. Verify it with the same recipe in reverse: recompute the
signature over the received X-Timestamp, method, path, and raw body, then
compare it to X-Signature in constant time. Reject if they differ or if the
timestamp is outside ±300 s. Inbound calls carry no X-Key-Id — they’re
signed with your single outbound secret.
No SDK? No problem
Everything here is plain HTTP + postMessage. If you’d rather not take the
browser dependency, build directly against Authentication,
the S2S API, and messaging — the SDK is
optional sugar, not a requirement.