주요 콘텐츠로 건너뛰기

스토어 Get 웹훅

store.get 웹훅은 Game Hub 스토어에서 플레이어가 이용 가능한 아이템 목록을 불러옵니다. Aghanim은 스토어 방문 시마다 귀하의 서버를 호출하고, 그 응답을 기반으로 스토어를 실시간으로 렌더링합니다.

이 웹훅은 다음과 같은 경우에 트리거됩니다:

  • 플레이어가 Game Hub에 로그인할 때(프리페치).
  • 플레이어가 스토어를 열 때.
  • 플레이어가 구매를 할 때.
Get store webhook
Get store webhook

Integration modes

store.get은 세 가지 레벨에서 작동할 수 있습니다. 귀하의 설정에 맞는 레벨을 선택하세요 — 더 깊은 레벨이 항상 더 나은 것은 아닙니다.

ModeSKU 설정제어할 수 있는 항목
Layer 1Aghanim 대시보드에 저장플레이어별로 어떤 SKU를 어떤 순서로 표시할지
Layer 2Aghanim 대시보드에 저장특정 필드 재정의: 가격, 이름, 이미지, 번들 구성
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 웹훅을 처리하는 엔드포인트의 함수 템플릿입니다:

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",
}
],
}
]

  1. store.get 웹훅 처리를 위한 함수를 개발합니다.
  2. 엔드포인트를 사용 가능하게 설정합니다.
  3. Aghanim 계정 내에서 Game → Webhooks → Add webhook에서 store.get 이벤트 유형을 선택하여 엔드포인트를 등록합니다.

또는, Create Webhook API 방법을 사용하여 Aghanim 내에서 엔드포인트를 등록할 수 있습니다.

트리거 값

값설명
hub.login플레이어가 Game Hub를 열 때. 로그인 시 스토어를 프리페치합니다.
hub.store.open스토어가 열릴 때.
hub.purchase플레이어가 구매 버튼을 클릭할 때 — 아이템이 여전히 사용 가능한지 확인하기 위해.
order.captured결제 처리가 시작되기 직전 — 아이템이 여전히 사용 가능한지 확인하기 위해.
testDashboard에서 "Send test event"를 사용할 때.

store.get이 다른 이벤트 유형과 어떻게 관련되는지에 대해서는 전체 이벤트 및 트리거 매트릭스를 참조하세요.

요청 스키마

아래는 예시입니다 store.get 웹훅 요청:

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"
}

이벤트 스키마

Key유형설명
event_idstringAghanim에 의해 생성된 고유 이벤트 ID.
game_idstringAghanim 시스템에서의 귀하의 게임 ID.
event_typestring이벤트의 유형, store.get 이럴 경우.
event_timenumberThe time the event was sent, in Unix epoch time. May differ across duplicate deliveries of the same event.
event_dataEventData이벤트 특정 데이터가 포함되어 있으며, 상속된 객체에 대한 가능한 키가 포함됩니다.
idempotency_keystring웹훅 작업이 재시도되어도 한 번만 실행되도록 보장합니다.
request_idstring|null이벤트가 API 요청에 의해 트리거된 경우, 요청 ID가 포함됩니다.
sandboxboolean이 이벤트가 샌드박스 게임 환경에서 전송되었는지를 표시합니다.
triggerstring|null이벤트가 전송된 원인이 된 트리거입니다.
transaction_idstringAghanim이 생성한 거래 ID입니다. 이 ID는 동일한 거래 내에서 발생한 여러 이벤트에서 동일할 수 있습니다.
contextEventContext|null이벤트에 대한 컨텍스트 정보.

EventContext 스키마

Key유형설명
orderOrderContext|null해당되는 경우 이벤트와 관련된 주문 정보입니다.
playerPlayerContext|null플레이어 정보를 추가하려면 웹훅 설정에서 "플레이어 컨텍스트 추가"를 활성화하세요.

EventData 스키마

Key유형설명
player_idstring플레이어 인증을 위해 선택된 고유한 플레이어 ID.
is_anonymousboolean현재 사용자가 인증되지 않음(익명)인지 여부를 나타냅니다.
placement_keysstring[]|nullstore.get이 요청된 위치의 placement key 목록입니다.
category_slugsstring[]|nullstore.get이 요청된 위치의 카테고리 slug 목록입니다.
current_page_pathstring|null현재 열려 있는 페이지의 경로로, Game Hub 루트를 기준으로 한 상대 경로입니다(예: /store/offers).
localestring현재 플레이어의 로케일 코드(ISO 639-1)입니다. 가능한 로케일의 전체 목록은 Locales에서 확인하세요.

응답 스키마

다음 JSON 페이로드와 함께 200 OK를 반환합니다.

Key유형설명필수 여부
items[Item|BundleItem]스토어에서 플레이어가 이용 가능한 아이템.아니오
rolling_offersRollingOffer[]플레이어에게 제공되는 롤링 오퍼.아니오

Item 스키마

노트

단독 제품(통화, 장비, 소모품)에는 Item을 사용하세요.

Key유형설명필수 여부
skustring고유한 SKU 식별자. Layer 1 및 2의 경우: Aghanim 대시보드의 SKU와 일치해야 합니다. Layer 3(완전히 동적)의 경우: price와 name도 필수입니다.예
pricenumberUSD 센트 단위 가격. Layer 3에서 필수.아니오
namestring아이템 이름. Layer 3에서 필수.아니오
is_stackableboolean아이템을 여러 번 구매하여 플레이어 인벤토리에 스택할 수 있는지 여부.아니오
quantitynumber아이템 수량.아니오
descriptionstring아이템 설명.아니오
image_urlstring메인 이미지 URL.아니오
background_image_urlstring배경 이미지 URL.아니오
background_image_colorstring배경색.아니오
image_url_featuredstring추천 아이템으로 표시될 때의 이미지 URL.아니오
card_background_image_urlstring아이템 카드의 배경 이미지 URL.아니오
category_slugsstring[]아이템이 스토어에서 표시되는 위치를 결정하는 카테고리 slug. Aghanim 대시보드에 구성된 카테고리와 일치해야 합니다.아니오
start_atnumber아이템이 사용 가능해지는 Unix 타임스탬프.아니오
end_atnumber아이템이 만료되는 Unix 타임스탬프.아니오
max_purchasesnumber허용되는 최대 구매 수. 도달하면 아이템이 스토어에서 사라집니다.아니오
current_purchasesnumber이 플레이어의 현재 구매 수.아니오
price_template_idstring국가별 가격 책정을 위한 가격 템플릿의 ID. 템플릿 이름이 아니라 /v1/price_templates의 내부 ID(예: ptm_eGhmGhfwjVL)를 사용하세요.아니오
custom_badgestring커스텀 배지 텍스트.아니오
bonus_itemsWebhookItemBonus[]|Item[]이 아이템에 포함된 보너스 아이템.아니오
bonus_badgestring보너스를 설명하는 배지 텍스트.아니오
bonus_percentnumbernested_items의 첫 번째 스택 가능 아이템에 적용되는 보너스 퍼센트.아니오
bonus_fixednumbernested_items의 첫 번째 스택 가능 아이템에 적용되는 고정 보너스 값. 설정된 경우 bonus_percent를 재정의합니다.아니오
reward_points_fixednumber구매 시 지급되는 고정 수량의 Reward Points. 설정된 경우 reward_points_percent를 재정의합니다.아니오
reward_points_percentnumber구매 시 지급되는 Reward Points의 비율.아니오
metadataobject추가 데이터를 위한 키-값 쌍.아니오
view_optionItemViewOption스토어 외관: default, in_title.아니오
card_typeStoreCardType카드 표시 유형: default, featured.아니오
free_claimsFreeClaims아이템을 무료로 수령할 수 있도록 하는 설정.아니오
show_disabled_by_max_purchasesboolean최대 구매 수에 도달한 후 아이템을 비활성화된 것으로 표시할지 여부.아니오

BundleItem 스키마

노트

여러 아이템을 포함하는 제품에는 BundleItem을 사용하세요. nested_items가 추가된 점을 제외하면 Item과 동일합니다.

Key유형설명필수 여부
skustring고유한 SKU 식별자. Layer 3의 경우: price와 name도 필수입니다.예
pricenumberUSD 센트 단위 가격. Layer 3에서 필수.아니오
namestring번들 이름. Layer 3에서 필수.아니오
nested_itemsNestedItem[]번들에 포함된 아이템.아니오
descriptionstring번들 설명.아니오
image_urlstring번들 이미지 URL.아니오
background_image_urlstring배경 이미지 URL.아니오
background_image_colorstring배경색.아니오
image_url_featuredstring추천 아이템으로 표시될 때의 이미지 URL.아니오
category_slugsstring[]스토어 배치를 위한 카테고리 slug. Aghanim 대시보드의 카테고리와 일치해야 합니다.아니오
start_atnumber번들이 사용 가능해지는 Unix 타임스탬프.아니오
end_atnumber번들이 만료되는 Unix 타임스탬프.아니오
max_purchasesnumber허용되는 최대 구매 수.아니오
price_template_idstring가격 템플릿의 내부 ID(예: ptm_eGhmGhfwjVL).아니오
custom_badgestring커스텀 배지 텍스트.아니오
bonus_itemsWebhookItemBonus[]|Item[]번들에 포함된 보너스 아이템.아니오
bonus_badgestring보너스를 설명하는 배지 텍스트.아니오
bonus_percentnumbernested_items의 첫 번째 스택 가능 아이템에 적용되는 보너스 퍼센트.아니오
bonus_fixednumbernested_items의 첫 번째 스택 가능 아이템에 적용되는 고정 보너스 값. 설정된 경우 bonus_percent를 재정의합니다.아니오
reward_points_fixednumber구매 시 지급되는 고정 Reward Points. 설정된 경우 reward_points_percent를 재정의합니다.아니오
reward_points_percentnumber구매 시 지급되는 Reward Points의 비율.아니오
metadataobject추가 데이터를 위한 키-값 쌍.아니오
view_optionItemViewOption스토어 외관: default, in_title.아니오
card_typeStoreCardType카드 유형: default, featured.아니오
free_claimsFreeClaims번들을 무료로 수령할 수 있도록 하는 설정.아니오
show_disabled_by_max_purchasesboolean최대 구매 수에 도달한 후 비활성화된 것으로 표시.아니오

NestedItem 스키마

경고

번들 내부의 아이템. name과 image_url은 기술적으로는 선택 사항이지만 올바른 표시를 위해 실제로는 필수입니다 — name이 없으면 번들은 Hub에서 중첩 아이템 없이 렌더링됩니다.

Key유형설명필수 여부
skustring게임과 Aghanim 양쪽에서 일치하는 아이템 SKU.예
namestring번들 카드 내부 표시에 사용되는 아이템 이름. 실제로는 필수 — 생략하면 번들이 중첩 아이템 없이 렌더링됩니다.사실상 필수
quantitynumber아이템 수량. 스택 가능한 아이템에 해당.아니오
image_urlstring번들 카드 내부 표시에 사용되는 아이템 이미지 URL. 올바른 시각적 렌더링을 위해 실제로는 필수.사실상 필수
background_image_urlstring번들 카드 내부에서 이 아이템의 배경 이미지.아니오
is_featuredboolean이 아이템이 번들 목록에서 추천되는지 여부.아니오
metadataobject추가 데이터를 위한 키-값 쌍.아니오

WebhookItemBonus 스키마

Key유형설명필수 여부
skustring게임과 Aghanim 양쪽에서 일치하는 아이템 SKU.예
quantitynumber보너스 아이템의 수량. 스택 가능한 아이템에 해당.아니오

FreeClaims 스키마

Key유형설명필수 여부
enabledboolean아이템을 무료로 수령할 수 있는지 여부.예
max_claimsnumber기간 내에 아이템을 수령할 수 있는 최대 횟수.예
periodPeriodmax_claims를 재설정하는 데 사용되는 기간 창. 생략된 경우 제한이 재설정되지 않습니다.아니오
exceeded_claims_behaviorExceededClaimsBehavior최대 수령 횟수에 도달한 후의 동작: hide — 스토어에서 제거합니다; disable_with_timer — 표시는 유지하되 카운트다운과 함께 비활성화됩니다.아니오

기간 스키마

Key유형설명필수 여부
unitPeriodType기간 유형: month, week, day, hour.예
durationnumber지정된 단위로 표시된 기간(예: duration: 2, unit: day = 이틀 기간).예

RollingOffer 스키마

노트

롤링 오퍼에는 Hub 페이지에 배치된 전용 블록이 필요합니다. placement_key는 해당 블록의 키와 일치해야 합니다.

Key유형설명필수 여부
keystring구매 또는 수령 상태를 추적하는 데 사용되는 고유 오퍼 키.예
placement_keystring롤링 오퍼가 표시될 Hub 블록의 키.예
namestring롤링 오퍼 제목.예
descriptionstring롤링 오퍼 설명.예
rolling_itemsRollingItem[]롤링 오퍼에 포함된 아이템.예
background_image_urlstring배경 이미지 URL.아니오
background_sizestring배경 이미지 크기: contain, repeat, cover.아니오
expire_atnumber만료 Unix 타임스탬프. Hub는 카운트다운을 표시하고 도달하면 오퍼를 숨깁니다.아니오

RollingItem 스키마

Key유형설명필수 여부
skustring동적 번들 아이템의 고유 SKU 식별자.예
quantitynumber아이템 수량. 스택 가능한 아이템에 해당.아니오
is_free_itemboolean아이템을 무료로 수령할 수 있는지 여부.아니오

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 1Layer 2Layer 3
Abandoned Cart 리타게팅✅⚠️ Limited1❌
Insufficient Funds 캠페인✅⚠️ Limited1❌
Loyalty Program✅❌❌
Piggy Bank✅❌❌
Sequential Offers✅❌❌
Rolling Offers✅❌2❌
동적 할인 / 보너스 레이어링✅✅3✅3
가격 재정의—✅4✅

Abandoned Cart, Insufficient Funds, Loyalty Program, Piggy Bank 또는 Sequential Offers에 의존하는 경우 Layer 1 또는 하이브리드 방식(Aghanim 내 정적 SKU + Layer 1 개인화)을 사용하세요.

도움이 필요하신가요? 통합 팀 [email protected]으로 문의하세요.

Footnotes

  1. 아이템이 카탈로그에서 해석되므로, 이메일/알림에는 Layer 2에서 재정의한 값이 아니라 카탈로그의 price/name이 표시됩니다. ↩ ↩2

  2. 롤링 아이템은 카탈로그 SKU여야 합니다. 카탈로그에 없는 SKU는 조용히 제외되어 구매하거나 청구할 수 없습니다. ↩

  3. 동적 가격(is_custom_price) 주문에서는 아이템별 스태킹 상한이 해제되므로 할인과 보너스가 전체 명목 값으로 적용됩니다. ↩ ↩2

  4. 번들에서는 안정적으로 작동합니다. 대시보드에 이미 StoreItem이 구성된 일반(비번들) 아이템의 경우, 응답의 price 필드는 무시되고 대시보드 가격이 사용됩니다. ↩

도움이 필요하세요?
통합팀에 문의하십시오 [email protected]