XGサブスクリプション通知¶
このページでは、XGからゲームサーバーへ送信されるXGサブスクリプション通知について説明します。
XGサブスクリプション通知の概要¶
XGサブスクリプション通知は、サブスクリプションの状態変更イベント(購入、継続、キャンセル、返金など)をゲームサーバーに通知する仕組みです。
各ストア(App Store / Google Play / DMM GAMES)の通知形式の違いはXGが吸収し、統一されたフォーマットでゲームサーバーへ通知します。
通知受信時の処理フロー¶
ゲームサーバーがXGサブスクリプション通知を受信した際の処理フローは以下の通りです。
署名検証:
signedTransactionを公開鍵で検証・デコード重複チェック:
transactionIdで処理済みかどうかを確認利用権処理:
itemActionに応じて利用権を付与・回収(または処理なし)サブスクリプション情報の保存:
subscriptionStatusやtransactionIdなどを保存レスポンス返却: 成功時は
subscriptionTagを返却
詳細なシーケンス図は XGサブスクリプション通知受信シーケンス を参照してください。
サブスクリプション状態変更イベントと利用権処理¶
利用権の付与・回収・無効化¶
XGサブスクリプション通知の itemAction は、ゲームサーバーが行うべき利用権の処理を表します。
itemAction の値に応じて、以下のいずれかの処理を行ってください。
付与(grant)
サブスクリプションの購入・継続・復元時に、アイテム付与やコンテンツのアンロック等を行います。 付与時には、レスポンスでサブスクリプションタグを返却してください。XGが返却されたタグに応じたゲーム内通貨を発行します。
回収(revoke)
返金・払い戻し時に、付与済みのアイテムやアンロック済みのコンテンツを回収する処理です。
無効化とは異なり、付与した対象を個別に取消す必要があります。
回収対象の特定にはXGサブスクリプション通知の renewalTransactionId を使用します。付与時に renewalTransactionId をアイテムと紐づけて保存しておき、回収時に一致するアイテムを回収してください。
なし(none)
利用権の付与・回収は行いません。ただし、サブスクリプション状態が利用不可の場合は、サービス利用を制限してください(以下の「無効化」を参照)。
無効化
itemAction とは別に、サブスクリプション状態に基づいてサービスの利用可否を判断する必要があります。
サブスクリプション状態が保留中(on_hold)・停止(paused)・期限切れ(expired)のいずれかの場合は、機能解放などのサブスクリプション有効期間中のみ利用可能なサービスを制限してください(コンテンツのロック等)。
無効化はアイテムの回収とは異なり、付与済みアイテムの回収は発生しません。
サブスクリプション状態について¶
subscriptionStatus は、通知時点でのサブスクリプションの状態です。App Store: App Storeサーバー通知 にサブスクリプション状態が含まれています。ただし、通知が遅延した場合は正しい状態とは限らないため、遅延時のみ現在の状態を取得しています
Google Play: リアルタイム デベロッパー通知(RTDN) にはサブスクリプション状態が含まれないため、現在の状態を取得しています
DMM GAMES: バッチで現在の状態を取得し、その状態に応じてXGサブスクリプション通知を作成しています
イベントと利用権処理の対応¶
サブスクリプション状態は、各ストア(App Store / Google Play / DMM GAMES)のサブスクリプションのステータスを元にXGが決定しています。 サブスクリプション状態については、用語集 の「サブスクリプション状態」を参照してください。 App Store・Google Playの通知およびサブスクリプション状態変更イベントの対応は、App Storeサーバー通知とサブスクリプション状態一覧 および Google Play通知とサブスクリプション状態一覧 を参照してください。
以下の表は通常時の内容です。ストアからの通知の遅延などにより、イベントと利用権処理の対応は異なることがあります。詳細は 利用権の操作とストアからの通知の遅延 を参照してください。
イベント |
説明 |
サブスクリプション状態 |
サービス利用可否 |
利用権処理(商品に利用権がある場合) |
|---|---|---|---|---|
purchase |
サブスクリプションの購入 |
有効(active) |
利用可 |
付与(grant): アイテム付与、コンテンツのアンロック等 |
renewal |
サブスクリプションの継続 |
有効(active) |
利用可 |
付与(grant): アイテム付与、コンテンツのアンロック等 |
recovered |
サブスクリプションの復元 |
有効(active) |
利用可 |
付与(grant): アイテム付与、コンテンツのアンロック等 |
restart |
サブスクリプションの再開 |
有効(active) |
利用可 |
なし(none) |
canceled |
サブスクリプションのキャンセル |
キャンセル(canceled) |
有効期限まで利用可 |
なし(none) |
grace_period |
サブスクリプションの猶予期間 |
猶予期間(grace_period) |
利用可 |
なし(none) |
on_hold |
サブスクリプションの保留中 |
保留中(on_hold) |
利用不可 |
なし(none): 利用権を無効化(コンテンツのロック等) ※1 |
paused |
サブスクリプションの停止 |
停止(paused) |
利用不可 |
なし(none): 利用権を無効化(コンテンツのロック等) ※1 |
expired |
サブスクリプションの期限切れ |
期限切れ(expired) |
利用不可 |
なし(none): 利用権を無効化(コンテンツのロック等) ※1 |
revoked |
サブスクリプションの返金・払い戻し |
返金済み(revoked) |
利用不可 |
回収(revoke): アイテムの回収・コンテンツのロック等 |
注釈
※1 実際の利用権の有効・無効はサブスクリプション状態を元に判断してください。
イベントの定義については 用語集 の「サブスクリプション状態変更イベント」を参照してください。
利用権の操作とストアからの通知の遅延¶
ストアからの通知の届く順序が入れ替わった場合、itemAction は通常時と異なる値になることがあります。
処理が不要になる場合: 通知の順序が入れ替わると、
itemActionがnoneになることがあります。利用権の付与・回収は行わず、サブスクリプション状態の更新のみ行います。例: 継続(renewal)と返金・払い戻し(revoked)の通知が前後して届き、返金・払い戻しが先に処理された場合、後から届いた継続の
itemActionはnoneとなります。
リクエスト情報¶
エンドポイント¶
XG Developer Siteで登録したエンドポイントにXGからリクエストが送信されます。
HTTPメソッド¶
POST
ヘッダー¶
ヘッダー名 |
値 |
|---|---|
Content-Type |
application/json |
リクエストボディの構造¶
リクエストボディはJSON形式です。通知データは signedTransaction にJWS形式で格納されています。
ゲームサーバーでは以下の手順で通知を処理してください:
signedTransactionを公開鍵で署名検証・デコードするデコードした通知データ(JWSペイロード)から
eventやsubscriptionStatusなどの情報を取得するtransactionIdで重複チェックを行い、未処理の場合のみ処理を実行する
署名検証の詳細は 署名検証 を参照してください。
リクエストボディのパラメーター¶
パラメーター |
型 |
必須 |
説明 |
|---|---|---|---|
signedTransaction |
string |
○ |
JWS(JSON Web Signature)形式で署名された通知データ |
testNotification |
object |
- |
XG Developer Siteのテスト通知送信機能で送信した場合にのみ存在します |
リクエストボディのサンプル¶
signedTransaction が常に含まれます。testNotification も含まれます。{
"signedTransaction": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."
}
{
"signedTransaction": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
"testNotification": {
"environment": "sandbox",
"xgApplicationId": "dev",
"xgProjectId": "xg_sample"
}
}
JWS形式¶
リクエストボディは ヘッダー.ペイロード.署名 の形式(Base64URLエンコード)で構成されます。
署名アルゴリズムはEdDSA(Ed25519)を使用しています。
JWSヘッダー¶
{
"alg": "EdDSA",
"typ": "JWT"
}
JWSペイロード(Claims)¶
{
"exp": 1234567890,
"data": {
"xgProjectId": "xg_sample",
"xgApplicationId": "dev",
"xgUserId": "12345678-1234-5678-9abc-abcdef000001",
"environment": "sandbox",
"transactionId": "12345678-1234-5678-9abc-abcdef000001",
"purchaseStoreId": "appstore",
"purchaseTransactionId": "2000000012345678",
"renewalTransactionId": "2000000012345679",
"productId": "com.exnoa.xg.sample.appstore01_monthly",
"planId": "appstore01_monthly",
"groupId": "sub-product0001",
"event": "renewal",
"eventAt": "2026-02-13T00:00:00Z",
"trigger": "store_notification",
"storeNotificationStatus": "on_time",
"itemAction": "grant",
"subscriptionStatus": "active",
"subscriptionExpiryAt": "2026-02-13T00:00:00Z"
}
}
XG Developer Siteからテスト通知を送信した場合、JWSペイロードの data フィールドには以下の項目のみ含まれます。
通常の通知とは異なり、 xgUserId 、 transactionId 、 event などの項目は含まれません。
{
"exp": 1234567890,
"data": {
"xgProjectId": "xg_sample",
"xgApplicationId": "dev",
"environment": "sandbox"
}
}
dataフィールドの項目一覧¶
項目 |
型 |
説明 |
|---|---|---|
xgProjectId |
string |
XG ProjectID |
xgApplicationId |
string |
XG AppID |
xgUserId |
string |
XGユーザーID |
environment |
string |
XGの環境名(production / sandbox) |
transactionId |
string |
XGが発行した取引ID(UUID v4)。処理済み判定に使用します |
purchaseStoreId |
string |
購入したストア(appstore / googleplay / dmmgames) |
purchaseTransactionId |
string |
購入時にストアが発行した取引ID。詳細は 用語集 の「purchaseTransactionId」を参照してください |
renewalTransactionId |
string |
継続時にストアが発行した取引ID。詳細は 用語集 の「renewalTransactionId」を参照してください |
productId |
string |
商品ID |
planId |
string |
プランID 。月額・年額などの識別に使用します |
groupId |
string |
|
event |
string |
サブスクリプション状態変更イベント (purchase / renewal / recovered / canceled / restart / expired / paused / revoked / grace_period / on_hold) |
eventAt |
timestamp |
サブスクリプションの更新が行われた日時。詳細は 用語集 の「eventAt」を参照してください |
trigger |
string |
通知の生成元。詳細は 用語集 の「trigger」を参照してください |
storeNotificationStatus |
string |
ストアからの通知の遅延の有無(on_time / delayed)。詳細は 用語集 の「storeNotificationStatus」を参照してください |
itemAction |
string |
通知に応じてゲームサーバーが行う利用権の操作(付与・回収・なし)。詳細は 用語集 の「itemAction」を参照してください |
subscriptionStatus |
string |
サブスクリプション状態 (active / canceled / grace_period / on_hold / expired / paused / revoked) |
subscriptionExpiryAt |
timestamp |
サブスクリプションの有効期限。詳細は 用語集 の「有効期限」を参照してください |
subscriptionCancelAt |
timestamp |
サブスクリプションのキャンセル日時。キャンセル時(event=canceled)のみ含まれます。詳細は 用語集 の「キャンセル日時」を参照してください |
署名検証¶
公開鍵¶
公開鍵はPEM形式(Ed25519)で提供されます。 XG Developer Siteからダウンロードしてください。
検証手順¶
JWS文字列をヘッダー・ペイロード・署名に分割
ヘッダーの
algがEdDSAであることを確認公開鍵で署名を検証
exp(有効期限)を検証(時刻検証の許容範囲として数秒〜数分程度を推奨)dataフィールドから通知ボディを取得
サンプルコード¶
ライブラリ: github.com/golang-jwt/jwt/v5
package main
import (
"crypto/ed25519"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
// XgNotificationClaims はJWSペイロードの構造体
type XgNotificationClaims struct {
jwt.RegisteredClaims
Data json.RawMessage `json:"data"`
}
// parseSignedString はJWS文字列を検証し、dataフィールドを返します
// publicKeyPem はEd25519の公開鍵をPEM形式で指定してください
func parseSignedString(jwsString string, publicKey ed25519.PublicKey, leeway time.Duration) ([]byte, error) {
restoredClaims := &XgNotificationClaims{}
token, err := jwt.ParseWithClaims(
jwsString,
restoredClaims,
func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodEd25519); !ok {
return nil, fmt.Errorf("予期しない署名アルゴリズム: %v", token.Header["alg"])
}
return publicKey, nil
},
jwt.WithLeeway(leeway), // 数秒から数分程度の許容範囲を設定
)
if err != nil {
return nil, err
}
if !token.Valid {
return nil, errors.New("JWSトークンは有効ではありません")
}
return restoredClaims.Data, nil
}
ライブラリ: firebase/php-jwt
<?php
/**
* XGサブスクリプション通知の署名検証サンプルコード
*
* このコードを実行する前に、以下のコマンドでライブラリをインストールしてください
* composer require firebase/php-jwt
*
*/
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
/**
* parseSignedString はJWS文字列を検証し、dataフィールドを返します
*
* @param string $jwsString JWS形式の署名済み文字列
* @param string $publicKey Ed25519の公開鍵をBase64エンコードした文字列
* JWT::decode()の第2引数(Keyオブジェクト)に渡す値です。
* PEM形式の公開鍵から変換する場合は、以下の手順で変換してください:
* 1. PEM形式からBase64部分を抽出
* 2. Base64デコードしてDER形式に変換
* 3. 最後の32バイト(Ed25519公開鍵)を取得
* 4. その32バイトをBase64エンコードした文字列をこの引数に渡す
* @param int $leeway 有効期限の許容範囲(秒単位)。数秒から数分程度の許容範囲を設定してください。
* @return array 検証されたJWTのdataフィールドの内容(配列形式)
* @throws \Exception JWTの検証に失敗した場合
*/
function parseSignedString(string $jwsString, string $publicKey, int $leeway): array {
JWT::$leeway = $leeway; // 数秒から数分程度の許容範囲を設定
$decoded = JWT::decode($jwsString, new Key($publicKey, 'EdDSA'));
return json_decode(json_encode($decoded->data), true);
}
通知の重複処理防止¶
XGサブスクリプション通知は、ネットワークエラーやタイムアウトなどの理由により、同じ通知を複数回送信する可能性があります。
ゲームサーバーは、署名検証後に transactionId を使用して通知の重複を判定してください。
同じ transactionId の通知を既に処理済みの場合は、処理をスキップして成功レスポンスを返却してください。
これにより、同じ通知による利用権の重複付与やゲーム内通貨の重複発行を防ぐことができます。
レスポンス仕様¶
ゲームサーバーは通知処理の結果をレスポンスとして返却します。
成功時のレスポンス¶
HTTP ステータスコード 200 で以下のJSONを返却してください。
{
"subscriptionTag": "repeat"
}
subscriptionTag: ゲーム内通貨発行に使用するサブスクリプションタグを指定
サブスクリプションタグ¶
サブスクリプションタグは、ゲーム内通貨の発行数を切り替えるための仕組みです。
ゲームサーバーは、XGサブスクリプション通知のレスポンスで subscriptionTag を返却します。XGは、返却されたタグに対応するゲーム内通貨発行設定を基に、ゲーム内通貨を発行します。
itemAction が grant の場合、subscriptionTag の指定は必須です。ゲーム内通貨を発行しない場合でも、ゲーム内通貨を発行しないサブスクリプションタグを指定してください。
設定方法や活用例の詳細については 用語集 の「サブスクリプションタグ」を参照してください。
失敗時のレスポンス¶
HTTPステータスコード 4xx または 5xx を返却してください。 XGはリトライ処理を行います。