ウェブフック (1.0)
ウェブフックは、システムで発生するイベントに関する通知です。特定のイベントが発生すると、エクソーラはHTTPリクエストを送信し、イベントデータがアプリケーション に送信されます。通常、これはJSON形式のPOSTリクエストです。
イベントの例:
- アイテムカタログとのユーザーインタラクション
- 注文の決済またはキャンセル
設定されたイベントが発生すると、エクソーラはウェブフック経由でシステムにそれを通知します。その結果、次のようなアクションを実行できます:
- ユーザーの残高を補充する
- 支払いを返金する
- ユーザーアカウントから新しいアイテムをクレジットまたはデビットする
- サブスクリプションの提供を開始する
- 詐欺の疑いがある場合にユーザーをブロックする
支払い処理ウェブフックのワークフローの例:
注意
使用されるソリューションとその統合のタイプに応じて、ウェブフックのセットとインタラクションのシーケンスは、提供された例とは異なる場合があります。
エクソーラウェブフック統合のビデオガイド:
エクソーラ製品およびソリューションを使用する場合のウェブフック設定:
| 製品/ソリューション | 必須/任意 | ウェブフックは何に使用されますか |
|---|---|---|
| 決済ソリューション | 必須 |
|
| ストア | 必須 |
|
| ゲーム販売 | 任意 | ゲームキーの販売では、ユーザーの検証やアイテムの付与は必要ありません。支払いや注文キャンセルなどのイベントに関する情報を受け取りたい場合は、ウェブフックを接続することができます。 ウェブフックを接続する場合は、すべての受信した必要なウェブフックを処理することが重要です。 |
| サブスクリプション | 任意 | サブスクリプションの作成、更新、またはキャンセルに関する情報を受け取ります。または、API経由で情報をリクエストすることもできます。 |
| ウェブショップ | 必須 |
|
| デジタル配信ソリューション | 必須 |
ウェブフックの設定に関する詳細情報は、デジタル配信ソリューションのドキュメントを参照してください。 |
| ログイン | 任意 | イベント情報の受信:
ウェブフックの設定の詳細については、ログインに関するドキュメントを参照してください。 |
Business Account is the primary tool for configuring Xsolla features, as well as for working with analytics and transactions.
Xsolla develops tools for different business categories. To simplify navigation, these tools are grouped into separate accounts by target audience:
注意
Publisher Account is a legacy tool.
If you use products and solutions that require working with webhooks, enable and test the webhooks in your account and set up their processing.
When specific events occur, webhooks are sent sequentially. Therefore, if you do not process one of the webhooks, subsequent webhooks will not be sent. The list of required webhooks is presented below.
エクソーラ側では、サイトでアイテムを購入して返品する際に2 つのウェブフック送信オプションが設定されています。支払いと取引データ、および購入したアイテムに関する情報は、個別に提供することも、1つのウェブフックにまとめるこ ともできます。
まとめたウェブフックで情報を受け取ります:
If you registered in the account after January 22, 2025, you receive all the information in the Successful payment for order (order_paid) and Order cancellation (order_canceled) webhooks. In this case, you do not need to process the Payment (payment) and Refund (refund) webhooks.
個別のウェブフックで情報を受け取ります:
If you registered in Publisher Account on or before January 22, 2025, you receive the following webhooks:
- Payment (
payment) and Refund (refund) with information about payment data and transaction details. - Successful payment for order (
order_paid) and Order cancellation (order_canceled) with information about purchased items.
You need to process all incoming webhooks. To switch to the new option with receiving combined webhooks, contact your Customer Success Managers or email to [email protected].
インゲームストアと決済管理の完全な運用のためには、主要なウェブフックの処理を実装する必要があります。
まとめたウェブフックを受信する場合:
| ウェブフック名とタイプ | 説明 |
|---|---|
ユーザー検証 >ユーザー検証(user_validation) | ユーザーがゲームに登録されていることを確認するために、支払いプロセスのさまざまな段階で送信されます。 |
ゲームサービス > まとめたウェブフック >注文支払い完了(order_paid) | 支払いデータ、取引の詳細、購入されたアイテムに関する情報が含まれます。ウェブフックからのデータを使用して、ユーザーにアイテムを追加します。 |
ゲームサービス > まとめたウェブフック >注文キャンセル(order_canceled) | キャンセルされた支払のデータ、取引の詳細、および購入したアイテムに関する情報が含まれています。ウェブフックからのデータを使用して、購入されたアイテムを削除します。 |
個別のウェブフックを受信する場合:
| ウェブフック名とタイプ | 説明 |
|---|---|
ユーザー検証 >ユーザー検証(user_validation) | ユーザーがゲームに登録されていることを確認するために、支払いプロセスのさまざまな段階で送信されます。 |
決済ソリューション >支払い(payment) | 支払いデータと取引の詳細が含まれています。 |
ゲームサービス> 個別のウェブフック>注文支払い完了(order_paid) | 購入したアイテムに関する情報が含まれています。ウェブフックからのデータを使用して、ユーザーにアイテムを追加します。 |
決済ソリューション >返金(refund) | 支払いデータと取引の詳細が含まれています。 |
ゲームサービス > 個別のウェブフック >注文キャンセル(order_canceled) | 購入したアイテムとキャンセルされたトランザクションのIDに関する情報が含まれます。ウェブフックのデータを使用して、購入したアイテムを削除します。 |
If item catalog personalization is implemented on your application’s side, set up processing of Catalog personalization on the partner’s side webhook.
サブスクリプションプランを自動的に管理するには、主要なウェブフックの処理を実装する必要があります:
- User validation (
user_validation) — is sent at different stages of the payment process to ensure the user is registered in the game. - Payment (
payment) — is sent when an order is paid and contains payment data and transaction details. - Created subscription (
create_subscription) — is sent when a Payment webhook has been successfully processed or the user has purchased a subscription with a trial period. It contains the details of the purchased subscription and user data. Use the webhook data to add a subscription to the user. - Updated subscription (
update_subscription) — is sent when a subscription is renewed or changed, when a Payment webhook has been successfully processed. It contains the details of the purchased subscription and user data. Use the webhook data to extend the user's subscription or change the subscription parameters. - Refund (
refund) — is sent when an order is canceled and contains the canceled payment data and transaction details. - Canceled subscription (
cancel_subscription) — is sent when a Refund webhook has been successfully processed or the subscription was canceled for another reason. It contains information about the subscription and user data. Use the webhook data to deduct purchased subscriptions from the user.
ウェブフックの受信を有効にするには:
- In the project in the account go to the Settings > Webhooks section.
- In the Webhook server field, specify the URL of your server where you want to receive webhooks in the
https://example.comformat. You can also specify the URL you find in a tool for testing webhooks.
注意。
データ転送にはHTTPSプロトコルが使用されます。HTTPプロトコルはサポートされていません。
- 秘密鍵を生成します:
- 秘密鍵セクションで、「キーを追加」クリックします。
- 開いたモーダルウィンドウに、一般リストからこの項目を識別するためのキー名を入力してください。。
- 「キーを作成」クリックします。
- 「シークレットをコピー」をクリックし、作成されたキーをユーザー側で保存してください。
- 「完了」クリックします。
- キーが保存されたことを確認し、「OK、閉じる」をクリックします。
Notice
Key recommendations:
- Save the generated secret key on your side. You can view the key in the account only once when it is created.
- Don’t share your secret key with anyone.
- The secret key must be stored on your server and never in binaries or on the front end.
- 「ウェブフックを有効にする」をクリックします。
注意
ウェブフックをテストするには、webhook.siteのような専用のウェブサイトか、ngrokのようなプラットフォームを選択できます。
Notice
You can’t simultaneously send webhooks to different URLs. What you can do in the account is specify a URL for testing first, and then replace it with the real one.
ウェブフックの受信を無効にするには:
- In the project in the account, go to the Settings > Webhooks section.
- Click Disable webhooks.
秘密鍵を定期的に更新することで、統合のセキュリティを向上させることができます。プロジェクトでは最大5つの秘密鍵を作成し、ローテーションを有効にすることができます 。これを行うには:
- In the Settings > Webhooks section, click Add key.
- 開いたモーダルウィンドウに、一般リストからこの項目を識別するためのキー名を入力してください。。
- 「キーを作成」をクリックします。
- 「シークレットをコピー」をクリックし、作成されたキーをユーザー側で保存してください。
- 「完了」をクリックします。
- キーが保存されたことを確認し、「OK、閉じる」をクリックします。
Notice
Key recommendations:
- Save the generated secret key on your side. You can view the key in the account only once when it is created.
- Don’t share your secret key with anyone.
- The secret key must be stored on your server and never in binaries or on the front end.
プロジェクトごとに有効な秘密鍵は1つだけです。変更したい場合は、別のキーの行にある「有効にする」をクリックして操作を確定してください。新しいキーへの移行 が正常に完了した後は、無効化されたキーを削除することをお勧めします。
For the webhooks in the Payments and Store section, advanced settings are available. They will automatically appear under the General settings block after you click the Enable webhooks button.
注意
詳細設定が表示されない場合は、一般設定でウェブフック受信が接続されていることを確認し、テスト > 決済ソリューションとストアタブにいることを確認してください。
このセクションでは、ウェブフックでの追加情報の受信を設定できます。これを行うには、対応するスイッチをアクティブポジションに設定します。各権限の行は、設定の変更に よって影響を受けるウェブフックを示します。
| トグル | 説明 |
|---|---|
| 保存された決済アカウントに関する情報を表示します(2025年1月22日以前にパブリッシャーアカウントに登録し、個別のウェブフックを受信している場合にのみ表示されます)。 | 保存された決済方法に関する情報は、payment_accountカスタムオブジェクト。 |
| 保存された決済方法によるトランザクションに関する情報を表示します。 | 情報は、ウェブフックの以下のカスタムパラメータに渡されます:
|
ウェブフックにorderオブジェクトを追加します(2025年1月22日以前にパブリッシャーアカウントに登録し、個別のウェブフックを受信している場合にのみ表示されます)。 | 注文に関する情報は、決済ウェブフックのorderオブジェクトに渡されます。 |
| 機密データを含めず、必須のユーザーパラメータのみを送信します。 | ウェブフックでは、ユーザーに関する次の情報のみが渡されます:
|
| カスタムパラメータを送信します。 | カスタムトークンパラメータに関する情報は、ウェブフックで渡されます。 |
| カードのBINおよびサフィックスを表示します。 | ウェブフックには、銀行カード番号に関する以下の情報が渡されます:
|
| カードブランドを表示します。 | 決済に使用したカードのブランド。例えば、MastercardやVisaなど。 |
| 返金理由に関する情報を表示します。 | 返金理由に関する詳細情報。 |
| 国別の源泉徴収税および顧客獲得料を表示します。 | payment_details.country_whtとpayment_details.user_acquisition_feeオブジェクトがウェブフックに含めて送信されます。このトグルはデフォルトでオンになっています。 |
| 3DS情報を送信します。 | 3-Dセキュア認証に関するデータを含むcardsオブジェクトがウェブフックで渡されます。 |

ウェブフックをテストすると、ユーザー側とエクソーラ側の両方でプロジェクトが正しく設定されていることを確認できます。
If webhooks are set up successfully, a webhooks testing section is displayed below the webhooks setup section.
The testing section in the account varies depending on the webhook receiving option.
If you registered in the account after January 22, 2025, you receive combined webhooks:
2025年1月22日以前にパブリッシャーアカウントに登録した場合、個別のウェブフックが送信されます:
注意
テストセクションにテストがパスしていないという警告が表示された場合は、ウェブフックリスナーのウェブフック応答設定を確認します。テストでのエラーの理由はテスト結果に示されます。
例:
テストには、専門サイトwebhook.siteを使用します。
「無効な署名に対する応答のテスト」セクションにエラーが表示されます。
エクソーラが間違った署名を持つウェブフックを送信し、ハンドラーがINVALID_SIGNATUREエラーコードを指定する4xx HTTPコードで応答することを期待しているために発生します。
webhook.siteは、署名が間違ったウェブフックを含むすべてのウェブフックに応答して200 HTTPコードを送信します。期待される4xx HTTPコードが得られないため、テスト結果にエラーが表示されます。
まとめたウェブフックを使用したシナリオのテストプロセスを以下に説明します。
「決済ソリューションとストア」タブでは、次のウェブフックをテストできます:
- User validation (
user_validation) - Successful payment for order (
order_paid) - Order cancellation (
order_canceled)
ウェブフックをテストするには:
- ウェブフックのテストセクションで、「決済ソリューションとストア」タブに移動します。
- In the drop-down list, select the item type. If you haven’t set up this item type in the account yet, click the button to configure it. After creating the item, return to the webhook testing section and proceed to the next step.
- 必要なフィールドに入力します:
- ユーザーID — テスト時には、任意の文字と数字の組み合わせを使用できます。
- 「エクソーラ注文ID」フィールドに任意の値を入力してください。
- エクソーラインボイスID — エクソーラ側のトランザクションID。テスト時には、任意の数値を使用できます。
- インボイスID — ゲーム側のトランザクションID。テスト時には、任意の文字と数字の組み合わせを使用できます。これは成功した支払いに必須のパラメータではありませんが、トランザクショ ンIDをエクソーラ側のトランザクションIDとリンクさせるために渡すことができます。
- 金額 — 決済金額。テスト時には、任意の数値を使用できます。
- 通貨 — ドロップダウンリストから通貨を選択します。
- ドロップダウンリストからアイテムのSKUを選択し、数量を指定します。同じタイプの複数のアイテムを選択するには、「+」をクリックして新しい行に追加します。
- 「ウェブフックをテスト」をクリックします。
User validation, Successful payment for order and Order cancellation webhooks with the specified data are sent to the provided URL. The results of testing each webhook type are displayed below the Test webhooks button.
If the Use public user ID box is checked in your account in the **Settings
Integration settings** section, the User search webhook will also be sent to your webhook server URL and the testing result will be displayed.
各ウェブフックについて、成功したシナリオとエラーが発生したシナリオの両方の処理を設定する必要があります。
Note
To test webhooks, you should have at least one subscription plan created in the account in the Items catalog > Subscriptions section.
「サブスクリプション」タブでは、次のウェブフックをテストできます:
- User validation (
user_validation) - Payment (
payment)
注意
その他のサブスクリプション管理シナリオのテストに関する詳細情報については、統合ガイドを参照してください。
ウェブフックをテストするには:
- テストセクションで、サブスクリプションタブに移動します。
- 必要なフィールドに入力します:
- ユーザーID — テスト時には、任意の文字と数字の組み合わせを使用できます。
- エクソーラインボイスID — エクソーラ側のトランザクションID。テスト時には、任意の数値を使用できます。
- Public user ID — ID known to a user, e.g., an email or nickname. This field is displayed if you checked the Use public user ID box in your account in the Settings > Integration settings section.
- 金額 — 決済金額。テスト時には、任意の数値を使用できます。
- 通貨 — ドロップダウンリストから通貨を選択します。
- プランID — サブスクリプションプラン。ドロップダウンリストからプランを選択します。
- サブスクリプション製品 — ドロップダウンリストから製品を選択します(オプション)。このリストは、プロジェクトで製品が設定されている場合に表示されます。
- インボイスID — ゲーム側のトランザクションID。テスト時には、任意の文字と数字の組み合わせを使用できます。これは成功した支払いに必須のパラメータではありませんが、トランザクショ ンIDをエクソーラ側のトランザクションIDとリンクさせるために渡すことができます。
- 試行期間。お試し期間なしでのサブスクリプションの購入をテストす るか、またはサブスクリプションの更新 をテストするには、値を
0に指定します。
- 「テスト」をクリックします。
指定したURLに、データが入力されたウェブフックを受け取ります。各ウェブフックのテスト結果は、成功したシナリオとエラーが発生したシナリオの両方で、「テスト 」ボタンの下に表示されます。
Webhook listener is program code that allows receiving incoming webhooks at a specified URL address, generating a signature, and sending a response to the Xsolla webhook server.
注意
Pay Station PHP SDKライブラリには、ウェブフックを処理するための既製のクラスが含まれています。
アプリケーション側で、以下のIPアドレスからのウェブフックの受信を実装してください:
185.30.20.0/24185.30.21.0/24185.30.22.0/24185.30.23.0/2434.102.38.17834.94.43.20735.236.73.23434.94.69.4434.102.22.197
If you integrated Login product, additionally add processing webhooks from the following IP addresses:
34.94.0.8534.94.14.9534.94.25.3334.94.115.18534.94.154.2634.94.173.13234.102.48.3035.235.99.24835.236.32.13135.236.35.10035.236.117.164
制限:
- アプリケーションのデータベースに同じIDで成功したトランザクションが複数存在することは許可されていません。
- ウェブフックリスナーがデータベースにすでに存在するIDを持つウェブフックを受信した場合、このトランザクションの以前の処理結果を返す必要があります。ユーザーに重複 した購入をクレジットし、データベースに重複したレコードを作成することは推奨されません。
安全なデータ転送を確保するには、ウェブフックが実際にエクソーラサーバーから送信され、転送中に改ざんされていないことを確認する必要があります。これを行うには、受信 したリクエスト本文のペイロードに基づいて独自の署名を生成し、それを着信リクエストのauthorizationヘッダーで提供される署名と比較してください。両方 の署名が一致する場合、そのウェブフックは正規のものであり、安全に処理できます。
検証ステップ:
着信ウェブフックリクエストの
authorizationヘッダーから署名を取得します。ヘッダーの形式はSignature <signature_value>です。ウェブフックのリクエスト本文をJSON形式で取得してください。
注意
JSONペイロードは、受信したものをそのまま使用してください。ペイロード をパースしたり再エンコードしたりしないでください。フォーマットが変更され、署名検証が失敗する原因となります。
比較用に独自の署名を生成します:
- JSONペイロードとプロジェクトの秘密鍵を連結します。秘密鍵は文字列の末尾に追加してください。
- 結果の文字列にSHA-1暗号ハッシュ関数を適用します。結果は小文字の16進数文字列になります。
生成した署名を
authorizationヘッダーからの署名と比較してください。両者が一致すれば、ウェブフックは信頼できるものです。
以下に、署名生成の実装例を各種プログラミング言語で示します: C#、C++、Go、PHP、Node.js。
POST /your_uri HTTP/1.1
host: your.host
accept: application/json
content-type: application/json
content-length: 165
authorization: Signature 52eac2713985e212351610d008e7e14fae46f902
{
"notification_type":"user_validation",
"user":{
"ip":"127.0.0.1",
"phone":"18777976552",
"email":"[email protected]",
"id":1234567,
"name":"Xsolla User",
"country":"US"
}
}curl -v 'https://your.hostname/your/uri' \
-X POST \
-H 'authorization: Signature 52eac2713985e212351610d008e7e14fae46f902' \
-d '{
"notification_type":
"user_validation",
"user":
{
"ip": "127.0.0.1",
"phone": "18777976552",
"email": "[email protected]",
"id": 1234567,
"name": "Xsolla User",
"country": "US"
}
}'注
このコードサンプルは、.NET Framework 4.0以降、および.NET Coreやその他の最新の.NETバージョンと互換性があります。署名検証には、タイミング攻撃を防ぐために、ConstantTimeEqualsメソッドによる定数時間比較が使用されています。
using System;
using System.Security.Cryptography;
using System.Text;
public static class XsollaWebhookSignature
{
public static string ComputeSha1(string jsonBody, string secretKey)
{
// Concatenation of the JSON from the request body and the project's secret key
string dataToSign = jsonBody + secretKey;
using (SHA1 sha1 = SHA1.Create())
{
byte[] hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(dataToSign));
// Convert hash bytes to lowercase hexadecimal string
var hexString = new StringBuilder(hashBytes.Length * 2);
foreach (byte b in hashBytes)
{
hexString.Append(b.ToString("x2"));
}
return hexString.ToString();
}
}
public static bool VerifySignature(string jsonBody, string secretKey, string receivedSignature)
{
string computedSignature = ComputeSha1(jsonBody, secretKey);
string receivedSignatureLower = receivedSignature.ToLower();
// Use constant-time comparison to prevent timing attacks
return ConstantTimeEquals(computedSignature, receivedSignatureLower);
}
private static bool ConstantTimeEquals(string a, string b)
{
if (a.Length != b.Length)
{
return false;
}
int result = 0;
for (int i = 0; i < a.Length; i++)
{
result |= a[i] ^ b[i];
}
return result == 0;
}
}注
Convert.ToHexStringメソッドを使用するには、.NET 5.0 以降が必要です。
CryptographicOperations.FixedTimeEqualsメソッドの代わりにConstantTimeEqualsを使用することもできます。// For .NET 5.0 and later, you can use the more concise Convert.ToHexString method:
using System;
using System.Security.Cryptography;
using System.Text;
public static class XsollaWebhookSignature
{
public static string ComputeSha1(string jsonBody, string secretKey)
{
string dataToSign = jsonBody + secretKey;
using var sha1 = SHA1.Create();
byte[] hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(dataToSign));
return Convert.ToHexString(hashBytes).ToLower();
}
public static bool VerifySignature(string jsonBody, string secretKey, string receivedSignature)
{
string computedSignature = ComputeSha1(jsonBody, secretKey);
string receivedSignatureLower = receivedSignature.ToLower();
// Use constant-time comparison to prevent timing attacks
return ConstantTimeEquals(computedSignature, receivedSignatureLower);
}
private static bool ConstantTimeEquals(string a, string b)
{
if (a.Length != b.Length)
{
return false;
}
int result = 0;
for (int i = 0; i < a.Length; i++)
{
result |= a[i] ^ b[i];
}
return result == 0;
}
}注
.NET 7.0以降をお持ちの場合は、CryptographicOperations.FixedTimeEqualsメソッドを使用できます。
// For .NET 7.0+, you can use the built-in CryptographicOperations.FixedTimeEquals:
using System.Security.Cryptography;
public static bool VerifySignature(string jsonBody, string secretKey, string receivedSignature)
{
string computedSignature = ComputeSha1(jsonBody, secretKey);
byte[] computedBytes = Encoding.UTF8.GetBytes(computedSignature);
byte[] receivedBytes = Encoding.UTF8.GetBytes(receivedSignature.ToLower());
return CryptographicOperations.FixedTimeEquals(computedBytes, receivedBytes);
}#include <string>
#include <sstream>
#include <iomanip>
#include <openssl/sha.h>
class XsollaWebhookSignature {
public:
static std::string computeSha1(const std::string& jsonBody, const std::string& secretKey) {
// Concatenation of the JSON from the request body and the project's secret key
std::string dataToSign = jsonBody + secretKey;
unsigned char digest[SHA_DIGEST_LENGTH];
// Create SHA1 hash
SHA1(reinterpret_cast<const unsigned char*>(dataToSign.c_str()),
dataToSign.length(), digest);
// Convert to lowercase hexadecimal string
std::ostringstream hexStream;
hexStream << std::hex << std::setfill('0');
for (int i = 0; i < SHA_DIGEST_LENGTH; ++i) {
hexStream << std::setw(2) << static_cast<unsigned int>(digest[i]);
}
return hexStream.str();
}
static bool verifySignature(const std::string& jsonBody, const std::string& secretKey, const std::string& receivedSignature) {
std::string computedSignature = computeSha1(jsonBody, secretKey);
// Timing-safe comparison
if (computedSignature.length() != receivedSignature.length()) {
return false;
}
volatile unsigned char result = 0;
for (size_t i = 0; i < computedSignature.length(); ++i) {
result |= (computedSignature[i] ^ receivedSignature[i]);
}
return result == 0;
}
};package main
import (
"crypto/sha1"
"crypto/subtle"
"encoding/hex"
"strings"
)
type XsollaWebhookSignature struct{}
func (x *XsollaWebhookSignature) ComputeSha1(jsonBody, secretKey string) string {
// Concatenation of the JSON from the request body and the project's secret key
dataToSign := jsonBody + secretKey
// Create SHA1 hash
h := sha1.New()
h.Write([]byte(dataToSign))
signature := h.Sum(nil)
// Convert to lowercase hexadecimal string
return strings.ToLower(hex.EncodeToString(signature))
}
func (x *XsollaWebhookSignature) VerifySignature(jsonBody, secretKey, receivedSignature string) bool {
computedSignature := x.ComputeSha1(jsonBody, secretKey)
receivedSignatureLower := strings.ToLower(receivedSignature)
// Use constant time comparison to prevent timing attacks
return subtle.ConstantTimeCompare([]byte(computedSignature), []byte(receivedSignatureLower)) == 1
}<?php
class XsollaWebhookSignature
{
/**
* Compute SHA1 signature from webhook JSON body and secret key
*
* @param string $jsonBody The raw JSON body from webhook
* @param string $secretKey The project's secret key
* @return string The lowercase SHA1 signature
*/
public static function computeSha1(string $jsonBody, string $secretKey): string
{
// Concatenation of the JSON from the request body and the project's secret key
$dataToSign = $jsonBody . $secretKey;
// Generate SHA1 signature
$signature = sha1($dataToSign);
return strtolower($signature);
}
/**
* Verify webhook signature using timing-safe comparison
*
* @param string $jsonBody The raw JSON body from webhook
* @param string $secretKey The project's secret key
* @param string $receivedSignature The signature from authorization header
* @return bool True if signature is valid, false otherwise
*/
public static function verifySignature(string $jsonBody, string $secretKey, string $receivedSignature): bool
{
$computedSignature = self::computeSha1($jsonBody, $secretKey);
// Use hash_equals for timing-safe comparison
return hash_equals($computedSignature, strtolower($receivedSignature));
}
}
?>const crypto = require('crypto');
class XsollaWebhookSignature {
// IMPORTANT: jsonBody must be the raw JSON string exactly as received from Xsolla
static computeSha1(jsonBody, secretKey) {
// Concatenation of the JSON from the request body and the project's secret key
const dataToSign = jsonBody + secretKey;
// Create SHA1 hash
const hash = crypto.createHash('sha1');
hash.update(dataToSign, 'utf8');
// Convert to lowercase hexadecimal string
return hash.digest('hex').toLowerCase();
}
static verifySignature(jsonBody, secretKey, receivedSignature) {
const computedSignature = this.computeSha1(jsonBody, secretKey);
const cleanReceivedSignature = receivedSignature.toLowerCase();
// Check if signatures have the same length before using timingSafeEqual
if (computedSignature.length !== cleanReceivedSignature.length) {
return false;
}
try {
return crypto.timingSafeEqual(
Buffer.from(computedSignature, 'hex'),
Buffer.from(cleanReceivedSignature, 'hex')
);
} catch (error) {
// Return false if there's any error (e.g., invalid hex characters)
return false;
}
}
}ウェブフックの受信を確認するには、サーバーは以下を返す必要があります:
- 成功した応答の場合は、
200、201、または204HTTPコード。 400HTTP code with description of the problem if the specified user was not found or an invalid signature was passed. Your webhook handler may also return a5xxHTTP code in case of temporary issues on your server.
If the Xsolla server did not receive a response to Successful payment for order and Order cancellation webhooks or received a response with a 5xx code, the webhooks are resent according to the following schedule:
- 5分間隔で2回の試行
- 15分間隔で7回の試行
- 60分間隔で10回の試行
ウェブフックの送信は、最初の送信から12時間以内に最大20回まで試行されます。
注意
以下の条件がすべて満たされている場合でも、支払いはユーザーに返金されます:
- 返金はエクソーラによって開始されました。
- ウェーブフックに対する応答として、
4xxステータスコードが返された、またはすべての再試行後に応答がなかった、あるいは5xxステータスコードが返された場合。
If the Xsolla server did not receive a response to the User validation webhook or received a response with a code of 400 or 5xx, the User validation webhook is not resent. In this case, the user sees an error and the Payment and Successful payment for order webhooks are not sent.
HTTPコード400のエラーコード:
| コード | メッセージ |
|---|---|
| INVALID_USER | 無効なユーザー |
| INVALID_PARAMETER | 無効なパラメータ |
| INVALID_SIGNATURE | 無効な署名 |
| INCORRECT_AMOUNT | 不正確な金額 |
| INCORRECT_INVOICE | 不正確なインボイス |
HTTP/1.1 400 Bad Request
{
"error":{
"code":"INVALID_USER",
"message":"Invalid user"
}
}以下のガイドラインに従ってください:
- 有効な証明書を備えたHTTPSのみを使用してください。
- 署名の検証は必ずリクエスト本文の生データに対して行ってください。データの解析や再エンコードは行わないでください
- 機密データをURLに含めたり、エラーメッセージに技術的な詳細情報を記載したりしないでください。
- ウェーブフックのエンドポイントをCSRFミドルウェアの対象から除外してください — エクソーラからの受信リクエストにはCSRFトークンが含まれていないため、この設定がないと拒否されます。
- エクソーラIPアドレスを許可リストに追加してください。
以下のガイドラインに従ってください:
- 本文とヘッダーを変更することなく、そのままの状態で
POSTリクエストを受け取ります。 - ウェブフックの署名を検証し 、適切なステータスコードを返してください。
4xx— 署名が一致しない場合。2xx—成功した場合。メインのビジネスロジックを実行する前に、「204 No Content」を返すことを推奨します。「200 OK」も受け入れ可能です。
- ペイロードを非同期ジョブまたはキューに渡し、さらなる処理を行います。
- 冪等性を実装してくだ さい。同じウェーブフックを複数回受信した場合でも、システムが適切に処理できるようにする必要があります。
フローの例:
HTTP POST /webhooks/xsolla
read raw_body, headers
if !verify_signature(raw_body, headers['authorization']):
return 400 {"error":{"code":"INVALID_SIGNATURE","message":"Invalid signature"}}
enqueue(raw_body)
return 204 # or 200以下のガイドラインに従ってください:
- トランザクションIDおよび/または external ID、注文IDを冪等性キーとし て使用してください。
- 処理済みのIDを保存し、重複するリクエストを受信した場合は前回の結果を返却します。
- アイテムの重複付与、データベースへの二重登録、および二重課金を回避してください。
- 逐次配信においては、前のイベントでエラーが発生すると、それ以降のすべてのイベントの処理がブロックされる点に注意してください。
以下のガイドラインに従ってください:
- サードパーティへのAPIコール、請求処理、アイテム付与などのリソース消費の激しい操作には、キューと非同期処理を使用してください。
- ウェブフックハンドラにタイムアウト(1〜3秒)を設定してください。一時的な障害の場合は、エクソーラのの再試行メカニズムを活用してください。
- フックハンドラ内で再試行を実装しないでください。再配信はエクソーラによって処理されます。
- ウェブフックの配信日時と処理ステータスをログに記録してください。また、5xx エラーの急増や再配信の発生に対するアラートを設定してください。
- ウェブフックに含まれる相関IDを、自システムのログやモニタリングシステム(APM)に伝播させてください。
- エラーログの記録とモニタリングをセットアップしてください。修復不可能な障害については、ジョブをデッドレターキュー(DLQ)に移動させてください。また、冪等性メカ ニズムによって保護された、イベントを再実行するための安全なツールを開発してください。
いいえ。支払いウェブフックはサーバー間プロトコルを使用しており、プロジェクト設定で指定された単一のURLに送信されます。ゲーム、ウェブサイト、またはモバイルアプリケーションで通知を受け取りたい場合は、お客様のサーバー側でウェ ブフックの送信設定を行い、エクソーラとお使いのゲーム間でデータを渡すようにしてください。また、開発者コンソールからウェブフックのテストを行うことも可能です。
Note
If you are testing the integration locally, POST requests from Xsolla don’t reach URLs like http://localhost:3000/my-webhook-endpoint. Use services such as Ngrok that allow you to create a tunnel for external access, enabling you to receive requests from Xsolla locally. E.g., you can read more about this in the ngrok documentation.
external IDを使用してください。これはお客様のシステム内の注文に割り当てられた、ゲーム内でのトランザクションIDを指します。エクソーラ側では、external IDはトランザクションIDにリンクされており、これによりエクソーラは同じトランザクションに対する重複支払いを防止できます。設定の詳細については、こちらのドキュ メントを参照してください。
以下の対応を推奨します:
- 署名検証後、直ちに「204」または「200」を返します。
- 変更を加えず、生のリクエスト本文に対してウェブフックの署名を検証します。
- すべての演算に対して冪等性を実装します。
- すべてのイベントをログに記録し、エラーモニタリングをセットアップします。
- URLに機密データを含めることを避け、エラーメッセージに技術的な詳細情報を記載したりしないでください。
詳細については、適用事例セクションを参照してください。
ウェブフックを正しく機能させるために、本番環境へのリリース前に以下のことが整っていることを確認してください:
- HTTPSが使用されています。
- ウェブフックの署名検証が、リクエスト本文を変更することなく、生データに対して実装されています。
- 署名が確認されるとすぐに「204/200」という応答が返されます。
- すべての操作に対して、冪等性が実装されています。
- エラーログの記録とモニタリングが設定されています。
- 機密データはURLには含まれず、技術的な詳細はエラーメッセージにも表示されません。
- エクソーラの再試行ロジックに基づき、ウェブフックの再試行がサポートされています。
- 統合プロセス全体がドキュメント化されています。
注意
通知タイプはnotification_typeパラメータで送信されます。
| ウェブフック | 通知タイプ | 説明 |
|---|---|---|
| ユーザー検証 | user_validation | ユーザーがゲーム内に存在するかどうかを確認するために送信されます。 |
| ユーザー検索 | user_search | パブリックユーザーIDに基づいてユーザー情報を取得するために送信されます。 |
| 決済 | payment | ユーザーが決済を完了した場合に送信されます。 |
| 返金 | refund | 何らかの理由で決済をキャンセルする必要がある場合に送信されます。 |
| 一部返金 | partial_refund | 何らかの理由で決済を一部キャンセルする必要がある場合に送信されます。 |
| 支払いが拒否されました | ps_declined | 支払いが決済システムによって拒否されたときに送信されます。 |
| AFSが拒否したトランザクション | afs_reject | AFSチェック中にトランザクションが拒否された場合に送信されます。 |
| AFSが更新したトランザクション | afs_black_list | AFSブロックリストが更新される場合に送信されます。 |
| 作成されたサブスクリプション | create_subscription | ユーザーがサブスクリプションを作成する場合に送信されます。 |
| 更新されたサブスクリプション | update_subscription | サブスクリプションが更新または変更された場合に送信されます。 |
| キャンセルされたサブスクリプション | cancel_subscription | サブスクリプションがキャンセルされた場合に送信されます。 |
| 非更新サブスクリプション | non_renewal_subscription | ステータスが非更新に設定される場合に送信されます。 |
| 決済アカウントを追加する | payment_account_add | ユーザーが支払いアカウントを追加または保存した場合に送信されます。 |
| 決済アカウントを削除する | payment_account_remove | ユーザーが保存済みアカウントから決済アカウントを削除する場合に送信されます。 |
| ウェブショップでのユーザー検証 | - | ウェブショップのサイトから送信され、ゲーム内にユーザーが存在するかどうかを確認します。 |
| パートナー側でのカタログ個人用設定 | partner_side_catalog | ユーザーがストアと直接交信する時に送信されます。 |
| 注文支払い完了 | order_paid | 注文が支払われたときに送信されます。 |
| 注文キャンセル | order_canceled | 注文がキャンセルされたときに送信されます。 |
| 紛争 | dispute | 新しい紛争手続きが開かれたときに送信されます。 |