Skip to documentation
GAME_ALLIGATOR
ProductsGamificationIntegrationDemos
Let’s talk
ProductsGamificationIntegrationDemos
Let’s talk
Integration
OverviewGuidesAPI referenceResources
Integration overview
Integration architectureFundamentalsGetting startedCertification
Products
Aggregation
Part I · Fundamentals
Core flowRequest & signingEnvelope, money and data formatsErrors: one modelIdempotency & retriesRate limits, pagination, networkMoney Path Rules
Part II · Operator API
Getting startedGames APIWallet APIFree rounds APIReports APIFeatures APIEvent stream
Part III · Certification
Run the checks from the Operator PortalThe command-line tool (for your CI)The checklistCertification checklist
Part IV · Changelog and status
ChangelogDocument ControlChangelog & migration guide: Operator API v2
Appendix
Numbers to rememberWhole guide on one page
API reference
Get player balanceAuthenticate player sessionDebit player balance (bet)Credit player balance (win/bonus)Atomic debit and creditRollback transactionClose game roundSettle free-round grantReconcile uncertain transactionNon-financial notificationgetCapabilitieslistGamesupdateGamelaunchGamelaunchDemocloseSessionlistSessionslistRoundsexportRoundsgetAggregatesissueGrantlistGrantscancelGrantsubscribeackgetOperatorCapabilitiesgetGameFeatureslistBonusBuyTypesissueBonusBuycancelBonusBuygetJackpotsgetBetRangesgetRoundReplaygetRoundDetailslistCampaignscancelCampaignqueryProviderTransactionslistTournamentsgetTournamentgetLeaderboard
Gamification
Integration
Quick startWidgets on your siteSigned-in playersLaunching gamesGameplay eventsTransportsOutcomes and your obligationsGame cataloguesRewardsOnboarding and go-liveReasons, numbers, currencies
Poker
Integration
OverviewEmbed the poker clientServer APIEvents and webhooks
llms-full.txt
Start here
Aggregation / Part I · FundamentalsCore flow

Game Alligators (GA) puts game studios' games into your casino. Traffic runs in two directions, each with its own key pair. Don't mix them. Direction Who calls whom What it's for Key you use Chapter You → GA You call https://api.rexplay.sit

Aggregation / Part I · FundamentalsRequest & signing

Every call GA sends to your wallet looks like this: http POST /v2/wallet/debit HTTP/1.1 Host: wallet.operator.example Content Type: application/json X API Key Id: key live 01 X Request Id: 0198a1d0 9a30 7f08 a7dd 713e4fd33db0 Idempotency Ke

Aggregation / Part I · FundamentalsEnvelope, money and data formats

Every wallet request has the same outer shape. payload is the action specific part and the per call ids live in payload.meta . json { "request id": "0198a1d0 9a30 7f08 a7dd 713e4fd33db0", "ts": "2026 09 13T12:00:00Z", "operator id": "0197aa

Aggregation / Part I · FundamentalsErrors: one model

Your wallet's codes are Appendix A.1. What GA returns to you is Appendix A.2. Every error from /v2/aggregator/ and /v2/features/ has a non 200 HTTP status and one JSON body, Content Type: application/json : json { "code": "ERROR CODE MAINTE

Aggregation / Part I · FundamentalsIdempotency & retries

Certification tests these hardest. Build them in from day one. The full normative text is in Money Path Rules. Keep every op id with its stored answer for at least 4 months . GA resends for 72 hours, and the margin covers reconciliation dis

Aggregation / Part I · FundamentalsRate limits, pagination, network

Your wallet must answer within 5 seconds per call (§2.7). Aim for well under 1 second. GA doesn't filter your source IP on the Operator API. If you restrict inbound traffic to your wallet, allow GA's outbound addresses: 49.13.169.177 and 46

Aggregation / Part I · FundamentalsMoney Path Rules

The rules below govern the v2 wallet contract when a call goes wrong. The cases are a timeout, a duplicate, a rollback of an operation you never saw, and a win that arrives after the session closed. They're additive: GA removes or renames n

Aggregation / Part II · Operator APIGetting started

1. Get sandbox credentials. Email [integration@gamealligator.com](mailto:integration@gamealligator.com). You receive an operator profile with operator id , the Operator API key pair, and a login to https://operator.rexplay.site . There you

↑ ↓ navigate↵ openesc close
  1. Home
  2. /Integration
  3. /Aggregation
  4. /Request & signing
Aggregation

Request & signing

MarkdownSource

Headers and signature: GA → your wallet

Every call GA sends to your wallet looks like this:

POST /v2/wallet/debit HTTP/1.1
Host: wallet.operator.example
Content-Type: application/json
X-API-Key-Id: key-live-01
X-Request-Id: 0198a1d0-9a30-7f08-a7dd-713e4fd33db0
Idempotency-Key: op-bet-48912
X-GA-Signature: t=1783944000,v1=8a75f608c2a8e178d89a055198050e9a2d5341c2f390ac1d2c2c68e433eb5f2d
  • X-API-Key-Id names the wallet signing key. It’s the key id you see in the Operator Portal.
  • X-Request-Id is this one network attempt. It equals request_id in the body and payload.meta.request_id, and changes on every retry.
  • Idempotency-Key equals payload.meta.op_id, the business operation. It stays the same across retries. Use it, or the body field, as your idempotency key.
  • X-GA-Signature carries the timestamp t (unix seconds, the same instant as the body’s ts) and the signature v1.

Verify in this order, before you parse the body:

  1. Find your secret by X-API-Key-Id.
  2. Split X-GA-Signature into t and v1.
  3. Reject if t is more than 300 seconds away from your clock.
  4. Compute SHA256_HEX of the raw body bytes exactly as received. Don’t re-serialize.
  5. Compute HMAC-SHA256(secret, t + "." + request_id + "." + body_sha256) where request_id is the top-level request_id field of the body (identical to the X-Request-Id header). Compare with v1 using a constant-time compare.
  6. Check that operator_id in the body is your operator id.

If any step fails, answer HTTP 401 or 403 with no body. GA treats that as “no answer” and retries. A bet that never verifies ends in a rollback.

Worked example

Use these values to test your verification code before GA sends anything. Secret, body, and timestamp are fixed, so your output must match to the byte.

InputValue
Wallet HMAC secretsec-cert-01
t1783944000 (the body’s ts 2026-07-13T12:00:00Z)
request_id0198a1d0-9a30-7f08-a7dd-713e4fd33db0
Raw body (one line, no trailing newline){"request_id":"0198a1d0-9a30-7f08-a7dd-713e4fd33db0","ts":"2026-07-13T12:00:00Z","operator_id":"0197aaaa-0000-7000-a000-000000000002","action":"get_balance","payload":{"meta":{"request_id":"0198a1d0-9a30-7f08-a7dd-713e4fd33db0","op_id":"op-bal-1001","operator_id":"0197aaaa-0000-7000-a000-000000000002","player_ref":"player-10428"},"currency":"EUR"}}
SHA256_HEX(body)f6ebf9ad22209d716b34fff30cb66d7232e96a2d403e86ed0dc23e5da9f8ad6b
Signing input1783944000.0198a1d0-9a30-7f08-a7dd-713e4fd33db0.f6ebf9ad22209d716b34fff30cb66d7232e96a2d403e86ed0dc23e5da9f8ad6b
Expected v18a75f608c2a8e178d89a055198050e9a2d5341c2f390ac1d2c2c68e433eb5f2d

Node.js:

const crypto = require("crypto");
function verify(rawBody, headers, secret) {
  const m = /t=(\d+),v1=([0-9a-f]{64})/.exec(headers["x-ga-signature"] || "");
  if (!m) return false;
  const [, t, v1] = m;
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const requestId = JSON.parse(rawBody).request_id;
  const bodySha = crypto.createHash("sha256").update(rawBody).digest("hex");
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${requestId}.${bodySha}`).digest();
  return crypto.timingSafeEqual(expected, Buffer.from(v1, "hex"));
}

PHP:

function verify(string $rawBody, array $headers, string $secret): bool {
    if (!preg_match('/t=(\d+),v1=([0-9a-f]{64})/', $headers['X-GA-Signature'] ?? '', $m)) return false;
    [, $t, $v1] = $m;
    if (abs(time() - (int)$t) > 300) return false;
    $requestId = json_decode($rawBody, true)['request_id'] ?? '';
    $input = $t . '.' . $requestId . '.' . hash('sha256', $rawBody);
    return hash_equals(hash_hmac('sha256', $input, $secret), $v1);
}

Go:

func verify(rawBody []byte, sigHeader, secret string) bool {
    var t int64; var v1 string
    if _, err := fmt.Sscanf(sigHeader, "t=%d,v1=%s", &t, &v1); err != nil { return false }
    if d := time.Now().Unix() - t; d > 300 || d < -300 { return false }
    var env struct{ RequestID string `json:"request_id"` }
    _ = json.Unmarshal(rawBody, &env)
    sum := sha256.Sum256(rawBody)
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(fmt.Sprintf("%d.%s.%s", t, env.RequestID, hex.EncodeToString(sum[:]))))
    want, _ := hex.DecodeString(v1)
    return hmac.Equal(mac.Sum(nil), want)
}

Python:

import hashlib, hmac, json, re, time
def verify(raw_body: bytes, sig_header: str, secret: str) -> bool:
    m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", sig_header or "")
    if not m: return False
    t, v1 = m.groups()
    if abs(time.time() - int(t)) > 300: return False
    request_id = json.loads(raw_body)["request_id"]
    signing_input = f"{t}.{request_id}.{hashlib.sha256(raw_body).hexdigest()}"
    expected = hmac.new(secret.encode(), signing_input.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

Java:

static boolean verify(byte[] rawBody, String sigHeader, String secret) throws Exception {
    var m = java.util.regex.Pattern.compile("t=(\\d+),v1=([0-9a-f]{64})").matcher(java.util.Objects.requireNonNullElse(sigHeader, ""));
    if (!m.matches()) return false;
    long t = Long.parseLong(m.group(1)); String v1 = m.group(2);
    if (Math.abs(System.currentTimeMillis() / 1000 - t) > 300) return false;
    String requestId = new com.fasterxml.jackson.databind.ObjectMapper().readTree(rawBody).get("request_id").asText();
    String bodySha = java.util.HexFormat.of().formatHex(java.security.MessageDigest.getInstance("SHA-256").digest(rawBody));
    var mac = javax.crypto.Mac.getInstance("HmacSHA256");
    mac.init(new javax.crypto.spec.SecretKeySpec(secret.getBytes(java.nio.charset.StandardCharsets.UTF_8), "HmacSHA256"));
    byte[] expected = mac.doFinal((t + "." + requestId + "." + bodySha).getBytes(java.nio.charset.StandardCharsets.UTF_8));
    return java.security.MessageDigest.isEqual(expected, java.util.HexFormat.of().parseHex(v1));
}

Signature: your calls to GA

Same header names (X-API-Key-Id, X-GA-Signature: t=…,v1=…), your Operator API key pair, the same 300-second window, but a different signing input:

SIGNING_INPUT = METHOD + "\n" + PATH + "\n" + t + "\n" + SHA256_HEX(raw_body)
v1            = HMAC-SHA256(operator_api_secret, SIGNING_INPUT)

METHOD is POST, PATH is the URL path only (/v2/aggregator/launch_game), t is unix seconds. The Postman collections GA_Aggregator_API_v2 and GA_Features_API_v2 compute this for you. Set ga_api_key_id and ga_hmac_secret.

Use these values to test your signing code before you call GA. Secret, body, and timestamp are fixed, so your output must match to the byte.

InputValue
Operator API HMAC secretsec-op-cert-01
METHODPOST
PATH/v2/aggregator/launch_game
t1783944000
Raw body (one line, no trailing newline){"player_ref":"player-10428","game_id":"pragmatic-vs20olympgate","currency":"EUR","token":"lt-cert-556677","return_url":"https://operator.example/lobby"}
SHA256_HEX(body)fd1f9b4dff076382033df7e7f9e575f62cebefb6cdc28276fc6e95ec4f411eb0
Signing inputPOST\n/v2/aggregator/launch_game\n1783944000\nfd1f9b4dff076382033df7e7f9e575f62cebefb6cdc28276fc6e95ec4f411eb0
Expected v1d1de5557c4a004e53eec6e34930e331638497c15884d17e2a649a75ce82d5283

The header you send is:

X-API-Key-Id: key-cert-op-01
X-GA-Signature: t=1783944000,v1=d1de5557c4a004e53eec6e34930e331638497c15884d17e2a649a75ce82d5283

Note the separator. Unlike §2.1’s GA→wallet signature (which joins t, request_id, and the body hash with .), the signing input here joins METHOD, PATH, t, and the body hash with newlines (\n). There is no trailing newline after the hash.

PreviousCore flowNextEnvelope, money and data formats
Integration support: integration@gamealligator.comIntegration center
On this page
Headers and signature: GA → your walletWorked exampleSignature: your calls to GA
↑ Back to top