When a player wins a reward that you pay out — cash, free spins, a jackpot — GA Promo tells your backend with a signed POST. Items, points and other in-promotion prizes stay inside GA Promo and need nothing from you.
9.1 The notification
POST <your reward endpoint>
Content-Type: application/json
X-Promo-Timestamp: <unix seconds>
X-Promo-Signature: <signature>
{
"delivery_id": "0199a7f2-5c1e-7c3a-9d41-2b7e8f0a1c55",
"player_ref": "u-123",
"player_id": "0198a1cb-4f37-7ee8-8e7d-08d39925aec0",
"operator_id": "<the GA operator id of the player's brand>",
"reward_type": "freespins",
"reward_value": "20",
"currency": "EUR",
"reference_id": "<campaign id>",
"timestamp": "2026-09-24T12:00:00Z"
}
| Field | Meaning |
|---|---|
delivery_id | this one grant; credit once per delivery_id |
player_ref | your id of the player — the value your events carry as player_ref (§5.1); present for every player your events have named, absent otherwise |
player_id | the same player’s id in GA |
operator_id | the GA operator id of the player’s brand |
reward_type | cash, freespins or jackpot |
reward_value | a decimal string: the amount in currency for cash and jackpot, the number of spins for freespins |
currency | the currency of the amount |
reference_id | the campaign that granted it; the same for every grant of that campaign — not a key |
timestamp | when the notification was built |
9.2 Checking it
expected = lowercase_hex( HMAC-SHA256( secret, X-Promo-Timestamp + "." + raw_body ) )
Compare expected with X-Promo-Signature in constant time, over the exact bytes you received. Refuse a timestamp more than 5 minutes away from your clock. The secret is the one we send you when we connect your reward endpoint (§9.3, §10.2). A request also carries X-Promo-Signature-Raw, an older signature over the body alone; do not rely on it.
9.3 Answering and retries
- Answer
200,202or204once the reward is credited or recorded to credit. Anything else, or no answer within 10 seconds, is a failure. - A failed notification is sent again with the same
delivery_idand body: 8 attempts in all, the pause growing from 10 seconds to at most 10 minutes. After the eighth failure we stop and resolve it with you by hand. - A repeat of a
delivery_idyou already credited must answer200and credit nothing.
You tell us the endpoint URL in the form (rewards.endpoint_url); each environment has its own URL and its own secret. The secret belongs to that URL: we generate it when we connect the endpoint and send it to you then, over the channel of §10.2 — so it comes only after you have given us a URL that GA can reach (a public https:// address; plain http:// only in the sandbox), not with your other credentials. Until the endpoint is connected, no reward notification is sent to you.
9.4 Delivery via Kafka
Instead of a webhook we can write each reward notification to a topic on your own Kafka cluster. You give us, in place of rewards.endpoint_url: the broker addresses (host:port, TLS required), the topic, the SASL mechanism (SCRAM-SHA-512 or SCRAM-SHA-256) and a user with its password, sent over the channel of §10.2. If you want to verify messages as in §9.2, say so and we send you a secret.
- Topic. You create it. We suggest
<env>.promo.rewards, for examplega-dev.promo.rewards. We never create topics. - Access. Grant our user
WRITEandDESCRIBEon that topic. - Message. The value is the JSON of §9.1, byte for byte. The key is
delivery_id. With a secret, the headersX-Promo-TimestampandX-Promo-Signatureare the ones of §9.2, computed over the record value. - Delivery. At least once: we write with
acks=alland count a notification delivered when the cluster acknowledges it. A failed write is repeated on the schedule of §9.3 (8 attempts, 10 seconds to 10 minutes). The samedelivery_idcan arrive more than once: credit once perdelivery_id. - Order. The key is
delivery_id, so records of one grant share a partition; order across grants is not guaranteed.