GamifyFlow

Integration guide

Getting your first player on the ladder

There are two halves to this. You publish events to a queue so the platform knows what your players are doing, and you call the API to read their standing back and draw it into your site. The first half is one message. The second is nine endpoints, all signed the same way.

Your first event

Publish to the queue you were given at onboarding. Nothing else has to be in place first — a player who has never been seen is created by their first event.

{
  "EventUid":      "3f6c1a9e-8d24-4c5b-9d0e-1a2b3c4d5e6f",
  "SchemaVersion": 1,
  "OccurredUtc":   "2026-08-28T09:14:02.481Z",
  "CustomerID":    "9f2c77b1",
  "Action":        "Bet",
  "Amount":        25.00,
  "Currency":      "EUR",
  "Provider":      "Pragmatic",
  "GameType":      "Casino",
  "GameID":        "2324"
}

EventUid is yours to generate and it is how duplicates are dropped. Publish the same uid twice — after a timeout, a retry, a redelivery — and the second one is ignored. Send a fresh uid for a genuinely new bet, and reuse the old one for a retry of the same bet.

CustomerID is your identifier for the player. The platform never asks you to adopt an id of ours, and never returns one.

How to know it worked. Call GET /v1/customers/9f2c77b1/gamification and you should see enrolled: true with points on the clock. That round trip — publish, then read — is the whole integration in miniature, and it is worth doing before you write any UI.

The two first-day mistakes

These are first, not in a troubleshooting section at the bottom, because between them they account for most of the time lost on a first integration. Both produce errors that do not say what is wrong.

1. Query parameters are signed in sorted order

?status=all&limit=25 is signed as ?limit=25&status=all. Order carries no meaning in a URL and carries a great deal in a signature, so the specification pins it: sort by name, then encode.

Send the query in whatever order you like on the wire. Sort it only when you build the string you sign. Getting this wrong gives you a 401 that says nothing about ordering, and every parameterless call you tried first will have worked perfectly.

2. Writes require an Idempotency-Key

Not optional, and not only for retries. Any request that changes something needs the header:

POST /v1/customers/9f2c77b1/awards/8812/redeem
Idempotency-Key: 0f6a5b2c-7d31-4e88-a0c9-b7e42f109d55

A request that times out never tells you whether it landed. Without a key, retrying it means doing it again — redeeming a prize twice, buying twice. With one, the second attempt returns the stored answer to the first and carries Idempotent-Replay: true so you can tell the two apart.

Signing a request

Four headers travel on every call:

HeaderWhat it is
X-GP-KeyYour API key. Public — it travels in the clear.
X-GP-TimestampUnix seconds. Must be within 300 s of our clock.
X-GP-NonceUnique per request. Remembered for 600 s, so a replay is refused.
X-GP-SignatureBase64 HMAC-SHA256 of the canonical string.

The canonical string is exactly five lines joined with \n — never \r\n, which is the third mistake people make on Windows:

GET
/v1/customers/CASUAL-0001/awards?limit=25&status=all
1755648060
c9f0f895fb98ab9159f51fd0297e236d
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

The signing key is PBKDF2-SHA256(secret, salt, iterations) truncated to 64 bytes. Derive it once at startup and keep it. At 310,000 iterations it is deliberately slow, and deriving it per request will dominate your latency.

A 401 never says which check failed. Not unhelpfulness: a precise reason tells somebody probing exactly which part to keep working on. If you are stuck, run the test vectors — they isolate the canonical string from the network, and a client that matches them is signing correctly.

Working clients in C#, Node, Python and PHP, and the vectors they are checked against, are shipped in docs/examples/signing/. Ask us and we will send them across.

Reading a player

One call gives you everything a level widget needs. It is deliberately one fat endpoint instead of five tidy ones: five would mean five round trips to draw one component, and five chances for one of them to fail.

GET /v1/customers/9f2c77b1/gamification

{
  "enrolled":     true,
  "level":        { "code": "GOLD", "name": "Gold", "ordinal": 3 },
  "levelPoints":  4820,
  "nextLevel":    { "code": "PLATINUM", "pointsRequired": 6000 },
  "tokenBalance": 1250,
  "awards":       { "claimable": 2 }
}
A player who has never played answers 200, not 404. You get enrolled: false, the entry level and zeros. To your site that player exists — they are logged in and looking at the widget — so treating them as missing would put an error where a starting position belongs.

Pass Accept-Language and level names and award descriptions come back in that language where a translation exists, falling back to the merchant's default.

The life of an award

An award moves through a small number of states, and your UI needs to say something different at each one.

StateWhat happenedWhat the player should see
AvailableGranted and waiting to be claimed.A button. This is the only actionable state.
RedeemingClaimed; we are delivering it to your provider.In progress. Usually seconds, occasionally longer if the provider is slow.
RedeemedDelivered, or nothing to deliver.Done, with the provider's reference where there is one.
ExpiredIts validity window passed unclaimed.Gone. Worth showing in history so the deadline feels real next time.
FailedThe provider refused it permanently.Nothing. It is in the operator's queue for a person to deal with.

Awards of pure token value have nothing to deliver, so they are settled the moment they are granted and go straight to Redeemed. Do not build a claim flow that assumes every award passes through Available.

What the errors mean

StatusMeaningWhat to do
400The request is malformed.Fix it. Retrying will not help.
401The signature did not verify, or the timestamp is outside the window.Check your clock first — it is more often that than the signature. Then the sorted query.
403Authenticated, but not for this player or this resource.Usually a player id belonging to another operator.
404No such award, item or purchase.Not used for players — see above.
409The state moved under you: already claimed, out of stock, cap reached.Re-read and show the player what is true now.
429Rate limited.Back off and honour Retry-After. Do not retry immediately in a loop.
5xxOurs.Retry with backoff, and keep the same Idempotency-Key on writes.

Credentials and rotation

A credential is a key, a secret, a salt and an iteration count. The key is public and travels on every request; the secret is shown once, when it is issued, and is stored only as a hash. There is no call that hands it back — if it is lost, the answer is to rotate.

Rotation issues a second credential while the first still works, so you deploy the new one and retire the old one afterwards, with no window where calls fail. Plan for two credentials to be valid at once; a client that can only hold one turns every rotation into an outage.

Never put the secret in a browser. Signing happens on your server. Anything shipped to a page can be read from it, and a leaked secret is a credential that can read every one of your players.

Full API reference Ask us something