Events & Payloads
Every webhook delivery sends a JSON payload with three fields:
| Field | Type | Description |
|---|---|---|
event | string | The event identifier |
data | object | Event-specific payload |
timestamp | string | ISO 8601 timestamp of when the event occurred |
points.awarded
Section titled “points.awarded”Fired when points are awarded to a user via POST /v1/points/award.
{ "event": "points.awarded", "data": { "ledgerId": "f1e2d3c4-b5a6-7890-1234-567890abcdef", "userId": "user_12345", "eventType": "signup", "pointsAwarded": 500, "userBalance": 5500, "reference": "ref_signup_user12345", "createdAt": "2025-07-15T10:00:00Z" }, "timestamp": "2025-07-15T10:00:00Z"}Data Fields
Section titled “Data Fields”| Field | Type | Description |
|---|---|---|
ledgerId | uuid | Unique ledger entry ID |
userId | string | The user who received points |
eventType | string | The event type that triggered the award |
pointsAwarded | integer | Points awarded |
userBalance | integer | User’s balance after the award |
reference | string | Idempotency reference (if provided) |
createdAt | datetime | When the award was created |
game.played
Section titled “game.played”Fired when a user plays a game via POST /v1/games/play.
{ "event": "game.played", "data": { "playId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "gameType": "NEON_WHEEL", "userId": "user_12345", "pointsSpent": 100, "userBalance": 4900, "outcome": { "winningSegment": 3, "spinAngle": 247.5 }, "reward": { "tier": "rare", "type": "cashback", "value": 50, "label": "$0.50 cashback" }, "environment": "LIVE", "playedAt": "2025-07-15T10:30:00Z" }, "timestamp": "2025-07-15T10:30:00Z"}Data Fields
Section titled “Data Fields”| Field | Type | Description |
|---|---|---|
playId | uuid | Unique play record ID |
gameType | string | NEON_WHEEL, COSMIC_SLOTS, or ENIGMA_BOXES |
userId | string | The user who played |
pointsSpent | integer | Points deducted |
userBalance | integer | Balance after deduction |
outcome | object | Game-specific outcome data |
reward.tier | string | epic, rare, or common |
reward.type | string | cashback, xp, points, item, or special |
reward.value | integer | Numeric reward value |
reward.label | string | Human-readable reward label |
environment | string | LIVE or TEST |
playedAt | datetime | When the game was played |
Handling Webhooks
Section titled “Handling Webhooks”Example Receivers
Section titled “Example Receivers”import express from 'express';import crypto from 'crypto';
const app = express();app.use(express.json());
const WEBHOOK_SECRET = 'whsec_your_secret_here';
app.post('/webhooks/gamifyhost', (req, res) => { // 1. Verify signature const signature = req.headers['x-webhook-signature']; const expected = 'sha256=' + crypto .createHmac('sha256', WEBHOOK_SECRET) .update(JSON.stringify(req.body)) .digest('hex');
if (signature !== expected) { return res.status(401).send('Invalid signature'); }
// 2. Handle event const { event, data } = req.body;
switch (event) { case 'points.awarded': console.log(`${data.userId} earned ${data.pointsAwarded} pts`); break; case 'game.played': console.log(`${data.userId} won ${data.reward.label}`); break; }
res.status(200).send('OK');});import hmacimport hashlibimport jsonfrom flask import Flask, request, abort
app = Flask(__name__)WEBHOOK_SECRET = "whsec_your_secret_here"
@app.route("/webhooks/gamifyhost", methods=["POST"])def handle_webhook(): # 1. Verify signature signature = request.headers.get("X-Webhook-Signature", "") payload = request.get_data(as_text=True) expected = "sha256=" + hmac.new( WEBHOOK_SECRET.encode(), payload.encode(), hashlib.sha256, ).hexdigest()
if not hmac.compare_digest(signature, expected): abort(401)
# 2. Handle event body = request.get_json() event = body["event"] data = body["data"]
if event == "points.awarded": print(f"{data['userId']} earned {data['pointsAwarded']} pts") elif event == "game.played": print(f"{data['userId']} won {data['reward']['label']}")
return "OK", 200package main
import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io" "net/http")
const webhookSecret = "whsec_your_secret_here"
func webhookHandler(w http.ResponseWriter, r *http.Request) { // 1. Read body body, _ := io.ReadAll(r.Body)
// 2. Verify signature sig := r.Header.Get("X-Webhook-Signature") mac := hmac.New(sha256.New, []byte(webhookSecret)) mac.Write(body) expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
if sig != expected { http.Error(w, "Invalid signature", 401) return }
// 3. Handle event var payload struct { Event string `json:"event"` Data map[string]interface{} `json:"data"` } json.Unmarshal(body, &payload)
switch payload.Event { case "points.awarded": fmt.Printf("%s earned %.0f pts\n", payload.Data["userId"], payload.Data["pointsAwarded"]) case "game.played": reward := payload.Data["reward"].(map[string]interface{}) fmt.Printf("%s won %s\n", payload.Data["userId"], reward["label"]) }
w.WriteHeader(200)}
func main() { http.HandleFunc("/webhooks/gamifyhost", webhookHandler) http.ListenAndServe(":3000", nil)}import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec;import java.util.HexFormat;
// Spring Boot example@RestControllerpublic class WebhookController {
private static final String SECRET = "whsec_your_secret_here";
@PostMapping("/webhooks/gamifyhost") public ResponseEntity<String> handleWebhook( @RequestBody String body, @RequestHeader("X-Webhook-Signature") String signature ) throws Exception { // 1. Verify signature Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec( SECRET.getBytes(), "HmacSHA256" )); String expected = "sha256=" + HexFormat.of().formatHex(mac.doFinal(body.getBytes()));
if (!signature.equals(expected)) { return ResponseEntity.status(401).body("Invalid"); }
// 2. Handle event var json = new ObjectMapper().readTree(body); String event = json.get("event").asText(); var data = json.get("data");
switch (event) { case "points.awarded" -> System.out.printf( "%s earned %d pts%n", data.get("userId").asText(), data.get("pointsAwarded").asInt() ); case "game.played" -> System.out.printf( "%s won %s%n", data.get("userId").asText(), data.get("reward").get("label").asText() ); }
return ResponseEntity.ok("OK"); }}<?php$secret = 'whsec_your_secret_here';$payload = file_get_contents('php://input');
// 1. Verify signature$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) { http_response_code(401); echo 'Invalid signature'; exit;}
// 2. Handle event$body = json_decode($payload, true);$event = $body['event'];$data = $body['data'];
switch ($event) { case 'points.awarded': error_log("{$data['userId']} earned {$data['pointsAwarded']} pts"); break; case 'game.played': error_log("{$data['userId']} won {$data['reward']['label']}"); break;}
http_response_code(200);echo 'OK';require 'sinatra'require 'openssl'require 'json'
WEBHOOK_SECRET = 'whsec_your_secret_here'
post '/webhooks/gamifyhost' do # 1. Read and verify payload = request.body.read signature = request.env['HTTP_X_WEBHOOK_SIGNATURE'] expected = 'sha256=' + OpenSSL::HMAC.hexdigest( 'sha256', WEBHOOK_SECRET, payload )
halt 401, 'Invalid signature' unless Rack::Utils.secure_compare(signature, expected)
# 2. Handle event body = JSON.parse(payload) event = body['event'] data = body['data']
case event when 'points.awarded' puts "#{data['userId']} earned #{data['pointsAwarded']} pts" when 'game.played' puts "#{data['userId']} won #{data['reward']['label']}" end
status 200 'OK'endusing System.Security.Cryptography;using System.Text;
// ASP.NET Minimal APIapp.MapPost("/webhooks/gamifyhost", async (HttpContext ctx) =>{ var secret = "whsec_your_secret_here";
// 1. Read body using var reader = new StreamReader(ctx.Request.Body); var body = await reader.ReadToEndAsync();
// 2. Verify signature var signature = ctx.Request.Headers["X-Webhook-Signature"] .ToString(); using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(body)); var expected = "sha256=" + Convert.ToHexString(hash).ToLower();
if (signature != expected) return Results.Unauthorized();
// 3. Handle event var json = JsonDocument.Parse(body).RootElement; var eventType = json.GetProperty("event").GetString(); var data = json.GetProperty("data");
Console.WriteLine(eventType switch { "points.awarded" => $"{data.GetProperty("userId")} earned " + $"{data.GetProperty("pointsAwarded")} pts", "game.played" => $"{data.GetProperty("userId")} won " + $"{data.GetProperty("reward").GetProperty("label")}", _ => $"Unknown event: {eventType}" });
return Results.Ok("OK");});