Item Add Webhook
Aghanim offers a centralized webhook that alerts your game to credit items to a player's account based on their activities within the game hub. This webhook is triggered whenever a player takes an action that requires item crediting. This guide provides detailed insights into the functionality and setup of this webhook.
This webhook is triggered in the following cases:
- A player makes a purchase on the game hub
- A player redeems a free item code on the game hub
- A player claims a daily reward or loyalty program reward
Each alert includes a reason field with the justification for item crediting.


Requirements
To use the item.add webhook from Aghanim, you should have the webhook server configured as follows:
- HTTPS endpoint, accepting POST webhook requests.
- Listen for events, generated and signed by Aghanim.
- Handle the
idempotency_keyincluded in the webhook payload to prevent processing duplicate webhooks. - Credit the specified item to a player's account.
- Respond with 2xx status codes to signal successful processing, and 4xx or 5xx for denial or errors.
Configuration
Below are function templates for an endpoint that processes Aghanim webhooks, alerting your game to credit items:
- Python
- Ruby
- Node.js
- Go
import fastapi, hashlib, hmac, json, typing
app = fastapi.FastAPI()
@app.post("/webhook")
async def webhook(request: fastapi.Request) -> dict[str, typing.Any]:
secret_key = "<YOUR_S2S_KEY>" # Replace with your actual webhook secret key
raw_payload = await request.body()
payload = raw_payload.decode()
timestamp = request.headers["x-aghanim-signature-timestamp"]
received_signature = request.headers["x-aghanim-signature"]
if not verify_signature(secret_key, payload, timestamp, received_signature):
raise fastapi.HTTPException(status_code=403, detail="Invalid signature")
data = json.loads(payload)
event_type = data["event_type"]
event_data = data["event_data"]
if event_type == "item.add":
add_item(event_data)
return {"status": "ok"}
raise fastapi.HTTPException(status_code=400, detail="Unknown event type")
def verify_signature(secret_key: str, payload: str, timestamp: str, received_signature: str) -> bool:
signature_data = f"{timestamp}.{payload}"
computed_hash = hmac.new(secret_key.encode(), signature_data.encode(), hashlib.sha256)
computed_signature = computed_hash.hexdigest()
return hmac.compare_digest(computed_signature, received_signature)
def add_item(event_data: dict[str, typing.Any]) -> None:
# Placeholder logic for processing the event and adding item.
# In a real application, this function would interact with your database or inventory system.
player_id = event_data["player_id"]
for item in event_data["items"]:
sku = event_data["sku"]
print(f"Item {sku} have been credited to player's {player_id} account.")
require 'sinatra'
require 'json'
require 'openssl'
post '/webhook' do
secret_key = "<YOUR_S2S_KEY>" # Replace with your actual webhook secret key
payload = request.body.read
timestamp = request.env["HTTP_X_AGHANIM_SIGNATURE_TIMESTAMP"]
received_signature = request.env["HTTP_X_AGHANIM_SIGNATURE"]
unless verify_signature(secret_key, payload, timestamp, received_signature)
halt 403, "Invalid signature"
end
data = JSON.parse(payload)
event_type = data["event_type"]
event_data = data["event_data"]
if event_type == "item.add"
add_item(event_data)
return { status: "ok" }.to_json
end
halt 400, "Unknown event type"
end
def verify_signature(secret_key, payload, timestamp, received_signature)
signature_data = "#{timestamp}.#{payload}"
computed_signature = OpenSSL::HMAC.hexdigest('sha256', secret_key, signature_data)
OpenSSL.secure_compare(computed_signature, received_signature)
end
def add_item(event_data)
# Placeholder logic for processing the event and adding item.
# In a real application, this function would interact with your database or inventory system.
player_id = event_data["player_id"]
for item in event_data["items"] do
sku = item["sku"]
puts "Item #{sku} has been credited to player's #{player_id} account."
end
end
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhook', express.raw({ type: "*/*" }), async (req, res) => {
const secretKey = '<YOUR_S2S_KEY>'; // Replace with your actual webhook secret key
const rawPayload = req.body;
const timestamp = req.headers['x-aghanim-signature-timestamp'];
const receivedSignature = req.headers['x-aghanim-signature'];
if (!verifySignature(secretKey, rawPayload, timestamp, receivedSignature)) {
return res.status(403).send('Invalid signature');
}
const payload = JSON.parse(req.body);
const { event_type, event_data } = payload;
if (event_type === 'item.add') {
addItem(event_data);
return res.json({ status: 'ok' });
}
return res.status(400).send('Unknown event type');
});
function verifySignature(secretKey, payload, timestamp, receivedSignature) {
const signatureData = `${timestamp}.${payload}`;
const computedSignature = crypto
.createHmac('sha256', secretKey)
.update(signatureData)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(computedSignature), Buffer.from(receivedSignature));
}
function addItem(event_data) {
// Placeholder logic for processing the event and adding item.
// In a real application, this function would interact with your database or inventory system.
const playerId = event_data.player_id;
for (const item of event_data.items) {
const sku = item.sku;
console.log(`Item ${item.sku} has been credited to player's ${playerId} account.`);
}
}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func webhookHandler(w http.ResponseWriter, r *http.Request) {
secretKey := "<YOUR_S2S_KEY>" // Replace with your actual webhook secret key
rawPayload, _ := ioutil.ReadAll(r.Body)
payload := string(rawPayload)
timestamp := r.Header.Get("X-Aghanim-Signature-Timestamp")
receivedSignature := r.Header.Get("X-Aghanim-Signature")
if !verifySignature(secretKey, payload, timestamp, receivedSignature) {
http.Error(w, "Invalid signature", http.StatusForbidden)
return
}
var data map[string]interface{}
if err := json.Unmarshal(rawPayload, &data); err != nil {
http.Error(w, "Invalid payload", http.StatusBadRequest)
return
}
eventType := data["event_type"].(string)
eventData := data["event_data"].(map[string]interface{})
if eventType == "item.add" {
addItem(eventData)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
return
}
http.Error(w, "Unknown event type", http.StatusBadRequest)
}
func verifySignature(secretKey, payload, timestamp, receivedSignature string) bool {
signatureData := fmt.Sprintf("%s.%s", timestamp, payload)
mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write([]byte(signatureData))
computedSignature := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(computedSignature), []byte(receivedSignature))
}
func addItem(eventData map[string]interface{}) {
// Placeholder logic for processing the event and adding item.
// In a real application, this function would interact with your database or inventory system.
playerID := eventData["player_id"].(string)
items := eventData["items"].([]interface{})
for _, item := range items {
itemMap := item.(map[string]interface{})
sku := itemMap["sku"].(string)
fmt.Printf("Item %s has been credited to player's %s account.\\n", sku, playerID)
}
}
Once your function is ready:
- Make your endpoint available.
- Register your endpoint within Aghanim account → Game → Webhooks → New Webhook by choosing the corresponding event type.
Alternatively, you can register your endpoint within Aghanim using the Create Webhook API method.
Request schema
Below is an example of an item.add webhook request:
- HTTP
- cURL
POST /your/webhook/uri HTTP/1.1
Content-Type: application/json
Host: your-webhook-endpoint.com
User-Agent: Aghanim/0.1.0
X-Aghanim-Signature: 2e45ed4dede5e09506717490655d2f78e96d4261040ef48cc623a780bda38812
X-Aghanim-Signature-Timestamp: 1725548450
{
"event_type": "item.add",
"event_data": {
"player_id": "2D2R-OP3C",
"items": [
{
"id": "itm_exTBZQmIlDz",
"name": "Crystals",
"description": "Reign supreme over your rivals with this massive crystal treasure.",
"sku": "crystals",
"quantity": 480000,
"price": 9499,
"price_decimal": 94.99,
"currency": "USD",
"type": "item",
"nested_items": null,
"fallback_item": null
}
],
"reason": "Order paid ord_eCacAulggpY"
},
"event_time": 1725548450,
"event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
"idempotency_key": "idmpt_aXRlb...JkX2VFS",
"request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
"sandbox": false,
"trigger": "order.paid",
"transaction_id": "whtx_eCacGbJVbvT",
"context": {
"order": {
"id": "ord_eCacAulggpY",
"amount": 9499,
"country": "US",
"currency": "USD",
"revenue_usd": 9499,
"fees": {
"payment_system_fee_usd": 2.85,
"aghanim_fee_usd": 14.25,
"taxes_usd": 7.65
},
"receipt_number": "1234567890",
"status": "paid",
"created_at": 1725548450,
"paid_at": 1725548460,
"creator": null
}
},
"game_id": "gm_exTAyxPsVwh"
}
curl "https://your-webhook-endpoint.com/your/webhook/uri" \
-X POST \
-H "Content-Type: application/json" \
-H "User-Agent: Aghanim/0.1.0" \
-H "X-Aghanim-Signature: 2e45ed4dede5e09506717490655d2f78e96d4261040ef48cc623a780bda38812" \
-H "X-Aghanim-Signature-Timestamp: 1725548450" \
-d '{
"event_type": "item.add",
"event_data": {
"player_id": "2D2R-OP3C",
"items": [
{
"id": "itm_exTBZQmIlDz",
"name": "Crystals",
"description": "Reign supreme over your rivals with this massive crystal treasure.",
"sku": "crystals",
"quantity": 480000,
"price": 9499,
"price_decimal": 94.99,
"currency": "USD",
"type": "item",
"nested_items": null,
"fallback_item": null
}
],
"reason": "Order paid ord_eCacAulggpY"
},
"event_time": 1725548450,
"event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
"idempotency_key": "idmpt_aXRlb...JkX2VFS",
"request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
"sandbox": false,
"trigger": "order.paid",
"transaction_id": "whtx_eCacGbJVbvT",
"context": {
"order": {
"id": "ord_eCacAulggpY",
"amount": 9499,
"country": "US",
"currency": "USD",
"revenue_usd": 9499,
"fees": {
"payment_system_fee_usd": 2.85,
"aghanim_fee_usd": 14.25,
"taxes_usd": 7.65
},
"receipt_number": "1234567890",
"status": "paid",
"created_at": 1725548450,
"paid_at": 1725548460,
"creator": null
}
},
"game_id": "gm_exTAyxPsVwh"
}'
The Event schema
| Key | Type | Description |
|---|---|---|
event_id | string | Unique Event ID generated by Aghanim. |
game_id | string | Your game ID in the Aghanim system. |
event_type | string | The type of the event, item.add in this case. |
event_time | number | Event date in Unix epoch time. |
event_data | EventData | Contains the event-specific data, with possible keys for inherited objects. |
idempotency_key | string | Ensures webhook actions are executed only once, even if retried. |
request_id | string|null | If the event was triggered by an API request, the request ID is included. |
sandbox | boolean | Indicates whether the event was sent from the sandbox game environment. |
trigger | string|null | The trigger that caused the event to be sent. |
transaction_id | string | The transaction ID generated by Aghanim. This ID may be the same for multiple events emitted within the same transaction. |
context | EventContext|null | Contextual information about the event. |
The EventContext schema
| Key | Type | Description |
|---|---|---|
order | OrderContext|null | Order information associated with the event if applicable. |
player | PlayerContext|null | (Optional) Player information. To add this, enable "Add player context" in the webhook settings. |
The EventData schema
| Key | Type | Description |
|---|---|---|
player_id | string | The unique Player ID chosen for player authentication. |
items | Item[] | An array of items to be credited to the player's account. |
reason | string | The human-readable reason that triggered the webhook. Example: Order paid ord_eCacAulggpY. |
The Trigger values
| Value | Description |
|---|---|
coupon.redeemed | When a coupon for a free item is redeemed. |
order.paid | When a purchase is completed successfully. |
hub.free_item_claim | When the player claims a free item in the store. |
hub.daily_reward_claim | When the player collects a reward for a daily reward program. |
hub.loyalty_claim | When the player collects a loyalty program reward. |
hub.progression_reward_claimed | When the player collects a progression reward. |
hub.rolling_offer_claimed | When the player claims reward in rolling offer. |
hub.pick_one_offer_claimed | When the player claims reward in pick one offer. |
hub.lootbox.open | When the player opens a lootbox after purchase. |
test | When using the "Send test event" in the Dashboard. |
The Item schema
| Key | Type | Description |
|---|---|---|
id | string | Item ID generated by Aghanim. |
name | string | Item name. |
description | string|null | Item description. |
sku | string | Item SKU matching on both the game and Aghanim sides. |
quantity | number | Item quantity. |
price | number|null | Item price in minor currency units. If the item is free, this field is null. |
price_decimal | number|null | Item price in decimal units. If the item is free, this field is null. |
currency | string|null | Item price currency. If the item is free, this field is null. |
type | string | The type of item. Possible values: item, bundle. |
nested_items | NestedItem[]|null | An array of items included in the bundle. |
fallback_item | Item|null | The item that may be given as fallback if the main item can't be given to the player. |
Item type bundle can contain several nested items inside.
You can handle the common sku of the bundle or handle each nested item individually.
The NestedItem schema
| Key | Type | Description |
|---|---|---|
id | string | Item ID generated by Aghanim. |
name | string | Item name. |
description | string|null | Item description. |
sku | string | Item SKU matching on both the game and Aghanim sides. |
quantity | number | Item quantity. |
type | string | The type of item. Possible values: item, bundle. |
nested_items | NestedItem[]|null | An array of items included in the bundle. |
The OrderContext schema
| Key | Type | Description |
|---|---|---|
id | string | Unique Order ID generated by Aghanim. |
receipt_number | string | Receipt number shown to the player in their receipt. |
amount | number | Order amount in local currency, in minor currency units. |
amount_before_discount | number | Order amount before discount in local currency, in minor currency units. |
payment_amount | number | Final amount charged to the customer in local currency, including taxes, in minor currency units. |
currency | string | Three-letter currency code (ISO 4217) of the order. |
payment_method | string | The payment method used, for example card, apple_pay, google_pay, paypal. |
country | string | Two-letter country code (ISO 3166-1 alpha-2) of the payment method country. |
payment_amount_usd | number | The payment amount converted to USD (e.g., 112.50 for $112.50) using estimated foreign exchange rates. The final value is confirmed after reconciliation with payment providers, typically by the 10th of the following month. |
revenue_usd | number | The expected net revenue, converted to USD cents using estimated foreign exchange rates and payment method fees. The final value is determined after reconciliation with payment providers, typically by the 10th of the following month. |
fees | Fees|null | Information about taxes and fees. The final values are determined after reconciliation with payment providers, typically by the 10th of the following month. |
status | string | Order status. Can be: created, captured, paid, canceled, refunded, refund_requested. |
sales_channel | string | Indicates the sales channel. Can be: game_hub, checkout, android_sdk, checkout_link. |
eligible_for_reward_points | number|null | The number of reward points the order is eligible for. |
eligible_for_loyalty_points | number|null | The number of loyalty points the order is eligible for. |
created_at | number | Order creation date in Unix epoch time. |
paid_at | number|null | Order payment date in Unix epoch time. |
creator | Creator|null | Information about creator associated with the order. |
metadata | object|null | The metadata object structured as key-value pairs to store additional information about the order. |
The Fees schema
| Key | Type | Description |
|---|---|---|
payment_system_fee_usd | number|null | Payment processing fee in USD cents. |
aghanim_fee_usd | number|null | Aghanim platform fee in USD cents. |
taxes_usd | number|null | Taxes in USD cents. |
The Creator schema
| Key | Type | Description |
|---|---|---|
name | string | Name of the creator. |
payout_decimal_usd | number | Creator payout amount in USD in major units. |
The PlayerContext schema
| Key | Type | Description |
|---|---|---|
player_id | string|null | The unique Player ID chosen for player authentication. |
player | object|null | Player ID components when using Composite Authorization. |
attributes | Attributes | Basic player attributes expected by Aghanim. |
custom_attributes | CustomAttributes | Custom player attributes. |
Optional: Rejecting purchases with automatic refunds
In some cases, your game server may need to reject a purchase after payment has been processed - for example, when a promotion has expired or a player doesn't meet purchase requirements. Aghanim supports automatic refund processing for these scenarios.
To reject a purchase, respond with a 400 status code:
{
"status": "error",
"code": "declined",
"message": "Purchase rejected: promotion has expired"
}
Automatic refunds must be configured by the Aghanim team. Please contact our integration team to enable automatic refunds for purchases rejected with code="declined".
For detailed information about implementing automatic refunds, including error codes, see Automatic Payment Refunds.
Need help?
Contact our integration team at integration@aghanim.com