스토어 Get 웹훅
store.get 웹훅은 Game Hub 스토어에서 플레이어가 이용 가능한 아이템 목록을 불러옵니다. Aghanim은 스토어 방문 시마다 귀하의 서버를 호출하고, 그 응답을 기반으로 스토어를 실시간으로 렌더링합니다.
이 웹훅은 다음과 같은 경우에 트리거됩니다:
- 플레이어가 Game Hub에 로그인할 때(프리페치).
- 플레이어가 스토어를 열 때.
- 플레이어가 구매를 할 때.


Integration modes
store.get은 세 가지 레벨에서 작동할 수 있습니다. 귀하의 설정에 맞는 레벨을 선택하세요 — 더 깊은 레벨이 항상 더 나은 것은 아닙니다.
| Mode | SKU 설정 | 제어할 수 있는 항목 |
|---|---|---|
| Layer 1 | Aghanim 대시보드에 저장 | 플레이어별로 어떤 SKU를 어떤 순서로 표시할지 |
| Layer 2 | Aghanim 대시보드에 저장 | 특정 필드 재정의: 가격, 이름, 이미지, 번들 구성 |
| Layer 3 | 완전히 동적 — Aghanim에 아무것도 저장하지 않음 | 모든 필드를 요청마다 실시간으로 전송 |
Layer 1 은 가장 가벼운 통합 방식이며 권장되는 시작점입니다. SKU와 그 시각적 구성은 Aghanim에 저장되며, 귀하는 플레이어별로 개인화된 목록과 표시 순서를 반환합니다. 이 레벨에서는 모든 Aghanim 네이티브 기능이 완전하게 작동합니다.
Layer 2 는 동일한 SKU가 사용자 컨텍스트에 따라 다른 파라미터를 필요로 할 때 — 예를 들어 진행도 기반 가격 책정, 구매 이력을 고려한 번들 구성, 사용자별 이미지 등 — 적합한 선택입니다. SKU는 여전히 Aghanim에 존재하며, 귀하는 플레이어별로 변경이 필요한 필드만 재정의합니다.
Layer 3 은 완전한 제어권을 제공하며, 개별 사용자 레벨에서 모든 수익화 로직을 관리하는 완전히 구축된 LiveOps 백엔드를 이미 보유한 팀에 적합합니다. 핵심 신호는 속도입니다. 오퍼가 몇 시간 내에 변하는 게임 내 상태를 반영해 야 하는 경우입니다. 이 레벨에서는 Aghanim에 폴백 구성이 없으므로 모든 아이템에 price와 name이 필수입니다. Layer 3은 귀하의 인프라를 Hub의 단일 장애점으로 만든다는 점에 유의하세요 — 백엔드 성능 저하는 스토어 가용성에 직접적인 영향을 미칩니다. 이 모드를 선택하기 전에 Limitations를 참조하세요.
store.get을 아예 사용하지 말아야 할 경우. 목표가 단순히 게임 내 카탈로그를 Hub에 그대로 반영하는 것이라면 store.get은 적합한 도구가 아닙니다. Hub의 가치는 게임 내에서 이미 이용 가능한 것을 복제하는 데서가 아니라, 독점성과 플레이어를 위한 추가 가치에서 나옵니다. 실제로는 상위 1015%의 SKU가 웹 상점 수익의 8090%를 창출합니다. 잘 구성된 오퍼 세트가 전체 카탈로그보다 더 나은 성과를 냅니다.
익명 사용자. event_data에서 is_anonymous: true인 경우, 빈 items 배열과 함께 200 OK를 반환하세요. 4xx나 5xx를 반환하지 마세요 — 이는 Hub에서 오류를 일으킵니다.
요구 사항
Aghanim의 store.get 웹훅을 사용하려면 웹훅 서버를 다음과 같이 구성해야 합니다:
- POST 웹훅 요청을 수락하는 HTTPS 엔드포인트.
- Aghanim이 생성하고 서명한 이벤트를 수신합니다.
- 성공적으로 처리되었음을 알리려면
2xx상태 코드로, 거부 또는 오류의 경우4xx또는5xx로 응답합니다. - 500ms 이내에 응답합니다. 응답이 느리면 Hub 경험이 저하됩니다.
구성
다음은 각 플레이어에게 개인화된 아이템 목록을 반환하는 store.get 웹훅을 처리하는 엔드포인트의 함수 템플릿입니다:
- Python
- Ruby
- Node.js
- Go
import fastapi, hashlib, hmac, json, typing
from fastapi.responses import JSONResponse
app = fastapi.FastAPI()
@app.post("/webhook")
async def webhook(request: fastapi.Request) -> dict[str, typing.Any]:
secret_key = "<YOUR_S2S_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 == "store.get":
if event_data.get("is_anonymous"):
return {"items": []}
items = get_store_items(event_data["player_id"])
return {"items": items}
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 get_store_items(player_id: str) -> list[dict]:
# Placeholder logic for fetching store items for this player.
# In a real application, this function would interact with your database or merchandising system.
return [
{
"sku": "starter_bundle",
"price": 999,
"name": "Starter Bundle",
"card_type": "featured",
"category_slugs": ["special-offers"],
"nested_items": [
{
"sku": "coins",
"name": "Coins",
"quantity": 500,
"image_url": "https://cdn.example.com/coins.png",
}
],
}
]
require 'sinatra'
require 'json'
require 'openssl'
post '/webhook' do
secret_key = "<YOUR_S2S_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 == "store.get"
if event_data["is_anonymous"]
return { items: [] }.to_json
end
items = get_store_items(event_data["player_id"])
return { items: items }.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 get_store_items(player_id)
# Placeholder logic for fetching store items for this player.
# In a real application, this function would interact with your database or merchandising system.
[
{
sku: "starter_bundle",
price: 999,
name: "Starter Bundle",
card_type: "featured",
category_slugs: ["special-offers"],
nested_items: [
{
sku: "coins",
name: "Coins",
quantity: 500,
image_url: "https://cdn.example.com/coins.png"
}
]
}
]
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>'; // 실제 웹훅 비밀 키로 교체하세요
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 === 'store.get') {
if (event_data.is_anonymous) {
return res.json({ items: [] });
}
const items = getStoreItems(event_data.player_id);
return res.json({ items });
}
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 getStoreItems(playerId) {
// Placeholder logic for fetching store items for this player.
// In a real application, this function would interact with your database or merchandising system.
return [
{
sku: 'starter_bundle',
price: 999,
name: 'Starter Bundle',
card_type: 'featured',
category_slugs: ['special-offers'],
nested_items: [
{
sku: 'coins',
name: 'Coins',
quantity: 500,
image_url: 'https://cdn.example.com/coins.png',
},
],
},
];
}
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>" // 실제 웹훅 비밀 키로 교체하세요
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 == "store.get" {
if isAnonymous, _ := eventData["is_anonymous"].(bool); isAnonymous {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{"items": []interface{}{}})
return
}
playerID, _ := eventData["player_id"].(string)
items := getStoreItems(playerID)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{"items": items})
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 getStoreItems(playerID string) []map[string]interface{} {
// Placeholder logic for fetching store items for this player.
// In a real application, this function would interact with your database or merchandising system.
return []map[string]interface{}{
{
"sku": "starter_bundle",
"price": 999,
"name": "Starter Bundle",
"card_type": "featured",
"category_slugs": []string{"special-offers"},
"nested_items": []map[string]interface{}{
{
"sku": "coins",
"name": "Coins",
"quantity": 500,
"image_url": "https://cdn.example.com/coins.png",
},
},
},
}
}
store.get웹훅 처리를 위한 함수를 개발합니다.- 엔드포인트를 사용 가능하게 설정합니다.
- Aghanim 계정 내에서 Game → Webhooks → Add webhook에서
store.get이벤트 유형을 선택하여 엔드포인트를 등록합니다.
또는, Create Webhook API 방법을 사용하여 Aghanim 내에서 엔드포인트를 등록할 수 있습니다.
트리거 값
| 값 | 설명 |
|---|---|
hub.login | 플레이어가 Game Hub를 열 때. 로그인 시 스토어를 프리페치합니다. |
hub.store.open | 스토어가 열릴 때. |
hub.purchase | 플레이어가 구매 버튼을 클릭할 때 — 아이템이 여전히 사용 가능한지 확인하기 위해. |
order.captured | 결제 처리가 시작되기 직전 — 아이템이 여전히 사용 가능한지 확인하기 위해. |
test | Dashboard에서 "Send test event"를 사용할 때. |
store.get이 다른 이벤트 유형과 어떻게 관련되는지에 대해서는 전체 이벤트 및 트리거 매트릭스를 참조하세요.
요청 스키마
아래는 예시입니다 store.get 웹훅 요청:
- 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": "store.get",
"event_data": {
"player_id": "2D2R-OP3C",
"is_anonymous": false,
"placement_keys": null,
"category_slugs": null,
"current_page_path": "/store",
"locale": "en"
},
"event_time": 1725548450,
"event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
"idempotency_key": "idmpt_aXRlb...JkX2VFS",
"request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
"sandbox": false,
"trigger": "hub.store.open",
"transaction_id": "whtx_eCacGbJVbvT",
"context": 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": "store.get",
"event_data": {
"player_id": "2D2R-OP3C",
"is_anonymous": false,
"placement_keys": null,
"category_slugs": null,
"current_page_path": "/store",
"locale": "en"
},
"event_time": 1725548450,
"event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
"idempotency_key": "idmpt_aXRlb...JkX2VFS",
"request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
"sandbox": false,
"trigger": "hub.store.open",
"transaction_id": "whtx_eCacGbJVbvT",
"context": null,
"game_id": "gm_exTAyxPsVwh"
}'
이벤트 스키마
| Key | 유형 | 설명 |
|---|---|---|
event_id | string | Aghanim에 의해 생성된 고유 이벤트 ID. |
game_id | string | Aghanim 시스템에서의 귀하의 게임 ID. |
event_type | string | 이벤트의 유형, store.get 이럴 경우. |
event_time | number | 유닉스 에포크 시간으로 된 이벤트 날짜. |
event_data | EventData | 이벤트 특정 데이터가 포함되어 있으며, 상속된 객체에 대한 가능한 키가 포함됩니다. |
idempotency_key | string | 웹훅 작업이 재시도되어도 한 번만 실행되도록 보장합니다. |
request_id | string|null | 이벤트가 API 요청에 의해 트리거된 경우, 요청 ID가 포함됩니다. |
sandbox | boolean | 이 이벤트가 샌드박스 게임 환경에서 전송되었는지를 표시합니다. |
trigger | string|null | The trigger that caused the event to be sent. |
transaction_id | string | Aghanim이 생성한 거래 ID입니다. 이 ID는 동일한 거래 내에서 발생한 여러 이벤트에서 동일할 수 있습니다. |
context | EventContext|null | 이벤트에 대한 컨텍스트 정보. |
EventContext 스키마
| Key | 유형 | 설명 |
|---|---|---|
order | OrderContext|null | 해당되는 경우 이벤트와 관련된 주문 정보입니다. |
player | PlayerContext|null | 플레이어 정보를 추가하려면 웹훅 설정에서 "플레이어 컨텍스트 추가"를 활성화하세요. |
EventData 스키마
| Key | 유형 | 설명 |
|---|---|---|
player_id | string | 플레이어 인증을 위해 선택된 고유한 플레이어 ID. |
is_anonymous | boolean | 현재 사용자가 인증되지 않음(익명)인지 여부를 나타냅니다. |
placement_keys | string[]|null | store.get이 요청된 위치의 placement key 목록입니다. |
category_slugs | string[]|null | store.get이 요청된 위치의 카테고리 slug 목록입니다. |
current_page_path | string|null | 현재 열려 있는 페이지의 경로로, Game Hub 루트를 기준으로 한 상대 경로입니다(예: /store/offers). |
locale | string | 현재 플레이어의 로케일 코드(ISO 639-1)입니다. 가능한 로케일의 전체 목록은 Locales에서 확인하세요. |
응답 스키마
다음 JSON 페이로드와 함께 200 OK를 반환합니다.
| Key | 유형 | 설명 | 필수 여부 |
|---|---|---|---|
items | [Item|BundleItem] | 스토어에서 플레이어가 이용 가능한 아이템. | 아니오 |
rolling_offers | RollingOffer[] | 플레이어에게 제공되는 롤링 오퍼. | 아니오 |
Item 스키마
단독 제품(통화, 장비, 소모품)에는 Item을 사용하세요.
| Key | 유형 | 설명 | 필수 여부 |
|---|---|---|---|
sku | string | 고유한 SKU 식별자. Layer 1 및 2의 경우: Aghanim 대시보드의 SKU와 일치해야 합니다. Layer 3(완전히 동적)의 경우: price와 name도 필수입니다. | 예 |
price | number | USD 센트 단위 가격. Layer 3에서 필수. | 아니오 |
name | string | 아이템 이름. Layer 3에서 필수. | 아니오 |
is_stackable | boolean | 아이템을 여러 번 구매하여 플레이어 인벤토리에 스택할 수 있는지 여부. | 아니오 |
quantity | number | 아이템 수량. | 아니오 |
description | string | 아이템 설명. | 아니오 |
image_url | string | 메인 이미지 URL. | 아니오 |
background_image_url | string | 배경 이미지 URL. | 아니오 |
background_image_color | string | 배경색. | 아니오 |
image_url_featured | string | 추천 아이템으로 표시될 때의 이미지 URL. | 아니오 |
card_background_image_url | string | 아이템 카드의 배경 이미지 URL. | 아니오 |
category_slugs | string[] | 아이템이 스토어에서 표시되는 위치를 결정하는 카테고리 slug. Aghanim 대시보드에 구성된 카테고리와 일치해야 합니다. | 아니오 |
start_at | number | 아이템이 사용 가능해지는 Unix 타임스탬프. | 아니오 |
end_at | number | 아이템이 만료되는 Unix 타임스탬프. | 아니오 |
max_purchases | number | 허용되는 최대 구매 수. 도달하면 아이템이 스토어에서 사라집니다. | 아니오 |
current_purchases | number | 이 플레이어의 현재 구매 수. | 아니오 |
price_template_id | string | 국가별 가격 책정을 위한 가격 템플릿의 ID. 템플릿 이름이 아니라 /v1/price_templates의 내부 ID(예: ptm_eGhmGhfwjVL)를 사용하세요. | 아니오 |
custom_badge | string | 커스텀 배지 텍스트. | 아니오 |
bonus_items | WebhookItemBonus[]|Item[] | 이 아이템에 포함된 보너스 아이템. | 아니오 |
bonus_badge | string | 보너스를 설명하는 배지 텍스트. | 아니오 |
bonus_percent | number | nested_items의 첫 번째 스택 가능 아이템에 적용되는 보너스 퍼센트. | 아니오 |
bonus_fixed | number | nested_items의 첫 번째 스택 가능 아이템에 적용되는 고정 보너스 값. 설정된 경우 bonus_percent를 재정의합니다. | 아니오 |
reward_points_fixed | number | 구매 시 지급되는 고정 수량의 Reward Points. 설정된 경우 reward_points_percent를 재정의합니다. | 아니오 |
reward_points_percent | number | 구매 시 지급되는 Reward Points의 비율. | 아니오 |
metadata | object | 추가 데이터를 위한 키-값 쌍. | 아니오 |
view_option | ItemViewOption | 스토어 외관: default, in_title. | 아니오 |
card_type | StoreCardType | 카드 표시 유형: default, featured. | 아니오 |
free_claims | FreeClaims | 아이템을 무료로 수령할 수 있도록 하는 설정. | 아니오 |
show_disabled_by_max_purchases | boolean | 최대 구매 수에 도달한 후 아이템을 비활성화된 것으로 표시할지 여부. | 아니오 |
BundleItem 스키마
여러 아이템을 포함하는 제품에는 BundleItem을 사용하세요. nested_items가 추가된 점을 제외하면 Item과 동일합니다.
| Key | 유형 | 설명 | 필수 여부 |
|---|---|---|---|
sku | string | 고유한 SKU 식별자. Layer 3의 경우: price와 name도 필수입니다. | 예 |
price | number | USD 센트 단위 가격. Layer 3에서 필수. | 아니오 |
name | string | 번들 이름. Layer 3에서 필수. | 아니오 |
nested_items | NestedItem[] | 번들에 포함된 아이템. | 아니오 |
description | string | 번들 설명. | 아니오 |
image_url | string | 번들 이미지 URL. | 아니오 |
background_image_url | string | 배경 이미지 URL. | 아니오 |
background_image_color | string | 배경색. | 아니오 |
image_url_featured | string | 추천 아이템으로 표시될 때의 이미지 URL. | 아니오 |
category_slugs | string[] | 스토어 배치를 위한 카테고리 slug. Aghanim 대시보드의 카테고리와 일치해야 합니다. | 아니오 |
start_at | number | 번들이 사용 가능해지는 Unix 타임스탬프. | 아니오 |
end_at | number | 번들이 만료되는 Unix 타임스탬프. | 아니오 |
max_purchases | number | 허용되는 최대 구매 수. | 아니오 |
price_template_id | string | 가격 템플릿의 내부 ID(예: ptm_eGhmGhfwjVL). | 아니오 |
custom_badge | string | 커스텀 배지 텍스트. | 아니오 |
bonus_items | WebhookItemBonus[]|Item[] | 번들에 포함된 보너스 아이템. | 아니오 |
bonus_badge | string | 보너스를 설명하는 배지 텍스트. | 아니오 |
bonus_percent | number | nested_items의 첫 번째 스택 가능 아이템에 적용되는 보너스 퍼센트. | 아니오 |
bonus_fixed | number | nested_items의 첫 번째 스택 가능 아이템에 적용되는 고정 보너스 값. 설정된 경우 bonus_percent를 재정의합니다. | 아니오 |
reward_points_fixed | number | 구매 시 지급되는 고정 Reward Points. 설정된 경우 reward_points_percent를 재정의합니다. | 아니오 |
reward_points_percent | number | 구매 시 지급되는 Reward Points의 비율. | 아니오 |
metadata | object | 추가 데이터를 위한 키-값 쌍. | 아니오 |
view_option | ItemViewOption | 스토어 외관: default, in_title. | 아니오 |
card_type | StoreCardType | 카드 유형: default, featured. | 아니오 |
free_claims | FreeClaims | 번들을 무료로 수령할 수 있도록 하는 설정. | 아니오 |
show_disabled_by_max_purchases | boolean | 최대 구매 수에 도달한 후 비활성화된 것으로 표시. | 아니오 |
NestedItem 스키마
번들 내부의 아이템. name과 image_url은 기술적으로는 선택 사항이지만 올바른 표시를 위해 실제로는 필수입니다 — name이 없으면 번들은 Hub에서 중첩 아이템 없이 렌더링됩니다.
| Key | 유형 | 설명 | 필수 여부 |
|---|---|---|---|
sku | string | 게임과 Aghanim 양쪽에서 일치하는 아이템 SKU. | 예 |
name | string | 번들 카드 내부 표시에 사용되는 아이템 이름. 실제로는 필수 — 생략하면 번들이 중첩 아이템 없이 렌더링됩니다. | 사실상 필수 |
quantity | number | 아이템 수량. 스택 가능한 아이템에 해당. | 아니오 |
image_url | string | 번들 카드 내부 표시에 사용되는 아이템 이미지 URL. 올바른 시각적 렌더링을 위해 실제로는 필수. | 사실상 필수 |
background_image_url | string | 번들 카드 내부에서 이 아이템의 배경 이미지. | 아니오 |
is_featured | boolean | 이 아이템이 번들 목록에서 추천되는지 여부. | 아니오 |
metadata | object | 추가 데이터를 위한 키-값 쌍. | 아니오 |
WebhookItemBonus 스키마
| Key | 유형 | 설명 | 필수 여부 |
|---|---|---|---|
sku | string | 게임과 Aghanim 양쪽에서 일치하는 아이템 SKU. | 예 |
quantity | number | 보너스 아이템의 수량. 스택 가능한 아이템에 해당. | 아니오 |
FreeClaims 스키마
| Key | 유형 | 설명 | 필수 여부 |
|---|---|---|---|
enabled | boolean | 아이템을 무료로 수령할 수 있는지 여부. | 예 |
max_claims | number | 기간 내에 아이템을 수령할 수 있는 최대 횟수. | 예 |
period | Period | max_claims를 재설정하는 데 사용되는 기간 창. 생략된 경우 제한이 재설정되지 않습니다. | 아니오 |
exceeded_claims_behavior | ExceededClaimsBehavior | 최대 수령 횟수에 도달한 후의 동작: hide — 스토어에서 제거합니다; disable_with_timer — 표시는 유지하되 카운트다운과 함께 비활성화됩니다. | 아니오 |
기간 스키마
| Key | 유형 | 설명 | 필수 여부 |
|---|---|---|---|
unit | PeriodType | 기간 유형: month, week, day, hour. | 예 |
duration | number | 지정된 단위로 표시된 기간(예: duration: 2, unit: day = 이틀 기간). | 예 |
RollingOffer 스키마
롤링 오퍼에는 Hub 페이지에 배치된 전용 블록이 필요합니다. placement_key는 해당 블록의 키와 일치해야 합니다.
| Key | 유형 | 설명 | 필수 여부 |
|---|---|---|---|
key | string | 구매 또는 수령 상태를 추적하는 데 사용되는 고유 오퍼 키. | 예 |
placement_key | string | 롤링 오퍼가 표시될 Hub 블록의 키. | 예 |
name | string | 롤링 오퍼 제목. | 예 |
description | string | 롤링 오퍼 설명. | 예 |
rolling_items | RollingItem[] | 롤링 오퍼에 포함된 아이템. | 예 |
background_image_url | string | 배경 이미지 URL. | 아니오 |
background_size | string | 배경 이미지 크기: contain, repeat, cover. | 아니오 |
expire_at | number | 만료 Unix 타임스탬프. Hub는 카운트다운을 표시하고 도달하면 오퍼를 숨깁니다. | 아니오 |
RollingItem 스키마
| Key | 유형 | 설명 | 필수 여부 |
|---|---|---|---|
sku | string | 동적 번들 아이템의 고유 SKU 식별자. | 예 |
quantity | number | 아이템 수량. 스택 가능한 아이템에 해당. | 아니오 |
is_free_item | boolean | 아이템을 무료로 수령할 수 있는지 여부. | 아니오 |
Example responses
Layer 1 — SKU 목록만 반환
{
"items": [
{ "sku": "crystals" },
{ "sku": "shield" },
{ "sku": "starter_bundle" }
]
}
Layer 2 — 특정 필드 재정의
{
"items": [
{
"sku": "starter_bundle",
"price": 999,
"nested_items": [
{ "sku": "crystals", "quantity": 200 },
{ "sku": "shield", "quantity": 1 }
]
}
]
}
Layer 3 — 완전히 동적
{
"items": [
{
"sku": "fly_bundle",
"price": 2000,
"name": "Fly Bundle",
"description": "Fly Bundle Description",
"image_url": "https://example.com/fly-bundle.png",
"card_type": "featured",
"card_background_image_url": "https://example.com/bg.png",
"category_slugs": ["special-offers"],
"start_at": 1630000000,
"end_at": 1635000000,
"max_purchases": 1,
"nested_items": [
{
"sku": "crystals",
"name": "Crystals",
"quantity": 100,
"image_url": "https://example.com/crystals.png"
},
{
"sku": "shield",
"name": "Shield",
"quantity": 1,
"image_url": "https://example.com/shield.png"
}
]
}
]
}
익명 사용자
{
"items": []
}
Limitations
Layer 2와 Layer 3은 여러 Aghanim 네이티브 기능과 호환성 격차가 있습니다.
| 기능 | Layer 1 | Layer 2 | Layer 3 |
|---|---|---|---|
| Abandoned Cart 리타게팅 | ✅ | ⚠️ Limited | ❌ |
| Insufficient Funds 캠페인 | ✅ | ⚠️ Limited | ❌ |
| Loyalty Program | ✅ | ❌ | ❌ |
| Piggy Bank | ✅ | ❌ | ❌ |
| Sequential Offers | ✅ | ❌ | ❌ |
| 동적 할인 / 보너스 레이어링 | ✅ | ⚠️ Limited | ⚠️ Limited |
이러한 기능에 의존하는 경우 Layer 1 또는 하이브리드 방식(Aghanim 내 정적 SKU + Layer 1 개인화)을 사용하세요.
도움이 필요하신가요? 통합 팀 integration@aghanim.com으로 문의하세요.
도움이 필요하세요?
통합팀에 문의하십시오 integration@aghanim.com