XGサブスクリプション通知

このページでは、XGからゲームサーバーへ送信されるXGサブスクリプション通知について説明します。

XGサブスクリプション通知の概要

XGサブスクリプション通知は、サブスクリプションの状態変更イベント(購入、継続、キャンセル、返金など)をゲームサーバーに通知する仕組みです。

各ストア(App Store / Google Play / DMM GAMES)の通知形式の違いはXGが吸収し、統一されたフォーマットでゲームサーバーへ通知します。

通知受信時の処理フロー

ゲームサーバーがXGサブスクリプション通知を受信した際の処理フローは以下の通りです。

  1. 署名検証: signedTransaction を公開鍵で検証・デコード

  2. 重複チェック: transactionId で処理済みかどうかを確認

  3. 利用権処理: itemAction に応じて利用権を付与・回収(または処理なし)

  4. サブスクリプション情報の保存: subscriptionStatustransactionId などを保存

  5. レスポンス返却: 成功時は subscriptionTag を返却

詳細なシーケンス図は XGサブスクリプション通知受信シーケンス を参照してください。

サブスクリプション状態変更イベントと利用権処理

利用権の付与・回収・無効化

XGサブスクリプション通知の itemAction は、ゲームサーバーが行うべき利用権の処理を表します。 itemAction の値に応じて、以下のいずれかの処理を行ってください。

付与(grant)

サブスクリプションの購入・継続・復元時に、アイテム付与やコンテンツのアンロック等を行います。 付与時には、レスポンスでサブスクリプションタグを返却してください。XGが返却されたタグに応じたゲーム内通貨を発行します。

回収(revoke)

返金・払い戻し時に、付与済みのアイテムやアンロック済みのコンテンツを回収する処理です。 無効化とは異なり、付与した対象を個別に取消す必要があります。 回収対象の特定にはXGサブスクリプション通知の renewalTransactionId を使用します。付与時に renewalTransactionId をアイテムと紐づけて保存しておき、回収時に一致するアイテムを回収してください。

なし(none)

利用権の付与・回収は行いません。ただし、サブスクリプション状態が利用不可の場合は、サービス利用を制限してください(以下の「無効化」を参照)。

無効化

itemAction とは別に、サブスクリプション状態に基づいてサービスの利用可否を判断する必要があります。 サブスクリプション状態が保留中(on_hold)・停止(paused)・期限切れ(expired)のいずれかの場合は、機能解放などのサブスクリプション有効期間中のみ利用可能なサービスを制限してください(コンテンツのロック等)。 無効化はアイテムの回収とは異なり、付与済みアイテムの回収は発生しません。

サブスクリプション状態について

XGサブスクリプション通知に含まれる 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 は通常時と異なる値になることがあります。

  • 処理が不要になる場合: 通知の順序が入れ替わると、itemActionnone になることがあります。利用権の付与・回収は行わず、サブスクリプション状態の更新のみ行います。

  • : 継続(renewal)と返金・払い戻し(revoked)の通知が前後して届き、返金・払い戻しが先に処理された場合、後から届いた継続の itemActionnone となります。

リクエスト情報

エンドポイント

XG Developer Siteで登録したエンドポイントにXGからリクエストが送信されます。

HTTPメソッド

POST

ヘッダー

ヘッダー名

Content-Type

application/json

リクエストボディの構造

リクエストボディはJSON形式です。通知データは signedTransaction にJWS形式で格納されています。

ゲームサーバーでは以下の手順で通知を処理してください:

  1. signedTransaction を公開鍵で署名検証・デコードする

  2. デコードした通知データ(JWSペイロード)から eventsubscriptionStatus などの情報を取得する

  3. transactionId で重複チェックを行い、未処理の場合のみ処理を実行する

署名検証の詳細は 署名検証 を参照してください。

リクエストボディのパラメーター

パラメーター

必須

説明

signedTransaction

string

JWS(JSON Web Signature)形式で署名された通知データ

testNotification

object

-

XG Developer Siteのテスト通知送信機能で送信した場合にのみ存在します

リクエストボディのサンプル

リクエストボディには signedTransaction が常に含まれます。
XG Developer Siteからテスト通知を送信した場合は、 testNotification も含まれます。
通常の通知
{
  "signedTransaction": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."
}
テスト通知(XG Developer Siteから送信)
{
  "signedTransaction": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "testNotification": {
    "environment": "sandbox",
    "xgApplicationId": "dev",
    "xgProjectId": "xg_sample"
  }
}

JWS形式

リクエストボディは ヘッダー.ペイロード.署名 の形式(Base64URLエンコード)で構成されます。 署名アルゴリズムはEdDSA(Ed25519)を使用しています。

JWSヘッダー

{
  "alg": "EdDSA",
  "typ": "JWT"
}

JWSペイロード(Claims)

通常の通知のJWSペイロード
{
  "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 フィールドには以下の項目のみ含まれます。 通常の通知とは異なり、 xgUserIdtransactionIdevent などの項目は含まれません。

テスト通知のJWSペイロード
{
  "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

サブスクリプショングループID

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からダウンロードしてください。

検証手順

  1. JWS文字列をヘッダー・ペイロード・署名に分割

  2. ヘッダーの algEdDSA であることを確認

  3. 公開鍵で署名を検証

  4. exp (有効期限)を検証(時刻検証の許容範囲として数秒〜数分程度を推奨)

  5. 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は、返却されたタグに対応するゲーム内通貨発行設定を基に、ゲーム内通貨を発行します。

itemActiongrant の場合、subscriptionTag の指定は必須です。ゲーム内通貨を発行しない場合でも、ゲーム内通貨を発行しないサブスクリプションタグを指定してください。

設定方法や活用例の詳細については 用語集 の「サブスクリプションタグ」を参照してください。

失敗時のレスポンス

HTTPステータスコード 4xx または 5xx を返却してください。 XGはリトライ処理を行います。