跳转到内容

概览

Webhook是系统中发生的事件的通知。当特定事件发生时,艾克索拉会向您的应用程序发送HTTP请求,其中会传输事件数据。它通常是JSON格式的POST请求。

事件示例:

  • 用户与商品目录交互
  • 付款或取消订单

当发生设定事件时,艾克索拉会通过Webhook通知您的系统。然后,您可以执行以下操作:

  • 补充用户余额
  • 进行退款
  • 向用户帐户发放或减除新商品
  • 开始提供订阅
  • 怀疑欺诈行为时封禁用户

支付处理Webhook工作流示例:

支付处理Webhook

注:

根据所使用的解决方案及其集成类型,Webhook集和交互顺序可能与示例中的不同。

艾克索拉Webhook集成视频指南:

使用艾克索拉产品和解决方案时的Webhook设置:

产品/解决方案必需/可选Webhook的用途是什么
付款必需
  • 用户验证。
  • 付款成功或退款时接收交易详情的信息。
  • 将购买的商品记入用户帐户,及在订单取消时将商品减除。
游戏内商店必需
  • 用户验证。
  • 付款成功或退款时接收交易详情的信息。
  • 将购买的商品记入用户帐户,及在订单取消时将商品减除。
游戏销售可选对于游戏密钥销售,用户验证和商品记入不是必需。如果想接收有关事件的信息(例如付款或订单取消),可以连接webhook。
如果连接Webhook,则必须处理所有传入的必需Webhook
订阅可选接收有关创建、更新或取消订阅的信息。您也可以通过API请求信息
网页商城必需
  • 用户验证。
  • 付款成功或退款时接收交易详情的信息。
  • 将购买的商品记入用户帐户,及在订单取消时将商品减除。
  • 用户认证(如果使用通过用户ID进行身份认证)。您也可以使用通过艾克索拉登录管理器进行用户认证
Digital Distribution Hub必需
  • 用户验证。
  • 将艾克索拉侧的交易ID与您系统中的交易ID关联。
  • 在订单中传输额外交易参数。
  • 将购买的商品记入用户帐户,及在订单取消时将商品减除。

请参阅文档,了解如何为Digital Distribution Hub设置Webhook。

登录管理器可选

接收事件信息:

  • 用户注册/授权
  • 用户邮箱地址确认
  • 关联用户的社交媒体帐户

有关设置Webhook的详细信息,请参阅登录管理器文档

必需Webhook列表

如果使用需要与Webhook交互的产品和解决方案,请在您的发布商帐户中启用并测试Webhook设置Webhook处理。当特定事件发生时,Webhook会按顺序发送。因此,如果您不处理其中一个Webhook,则不会发送后续Web hook。下面列出了必需Webhook的列表。

游戏内商店和付款

在艾克索拉侧已设置了2种Webhook发送选项,用于处理网站上的商品购买和退货——支付和交易数据信息以及已购商品信息可以分开发送,也可以合并为一个Webhook 发送。

在合并Webhook中接收信息:

如果您在2025年1月22日之后在发布商帐户注册,您将在订单成功支付(order_paid) 订单取消(order_canceled) Webhook中收到所有信息。在这种情况下,您无需处理支付(payment)和退款(refund) Webhook。

在单独Webhook中接收信息:

如果您在2025年1月22日或之前在发布商帐户注册,您将收到以下Webhook:

您需要处理所有收到的Webhook。如需切换到新的合并Webhook接收方式,请联系您的客户成功经理或发送邮件至csm@xsolla.com

为确保游戏内商店和支付管理功能正常运行,必须实现主要Webhook的处理。

如果接收合并Webhook:

Webhook名称和类型描述
用户验证 >用户验证 (user_validation)在支付流程的不同阶段发送,用于确保用户已在游戏中注册。
游戏服务 > 合并Webhook >订单成功支付(order_paid)包含支付数据、交易详情和已购商品信息。请使用Webhook中的数据为用户添加商品。
游戏服务 > 合并Webhook >订单取消(order_canceled)包含已取消支付的数据、交易详情和已购商品信息。请使用Webhook中的数据移除已购商品。

如果接收单独Webhook

Webhook名称和类型描述
用户验证 >用户验证 (user_validation)在支付流程的不同阶段发送,用于确保用户已在游戏中注册。
付款 >支付(payment)包含支付数据和交易详细信息。
游戏服务 > 单独Webhook >订单成功支付(order_paid)包含已购商品信息。请使用Webhook中的数据为用户添加商品。
付款 >退款 (refund)包含支付数据和交易详细信息。
游戏服务 > 单独Webhook >订单取消(order_canceled)包含已购商品信息和已取消交易的ID。请使用Webhook中的数据移除已购商品。

如果您的应用程序侧实现了商品目录个性化,请设置对合作伙伴侧的目录个性化Webhook的处理。

注意

要接收真实支付,您只需签署许可协议并实现以下Webhook的处理:

订阅

要自动管理订阅计划,需要实现主要Webhook的处理:

  • 用户验证(user_validation) — 在支付过程的不同阶段发送,以确保用户已在游戏中注册。
  • 支付(payment) — 在支付订单后发送,包含付款数据和交易详细信息。
  • 创建了订阅(create_subscription) — 支付Webhook已成功处理或用户购买了具有试用期的订阅时发送。它包含所购买的订阅 的详细信息和用户数据。使用该Webhook数据向用户添加订阅。
  • 更新了订阅(update_subscription) — 续订或更改订阅以及支付Webhook已成功 处理后发送。它包含所购买的订阅的详细信息和用户数据。使用该Webhook数据来延长用户的订阅或更改订阅参数。
  • 退款(refund) — 订单被取消后发送,包含取消的付款数据和交易详细信息。
  • 取消了订阅(cancel_subscription) — 退款Webhook已成功处理或订阅因其他原因被取消时发送。它包含有关订阅和用户数据的 信息。使用该Webhook数据扣除用户购买的订阅。

在发布商帐户中设置Webhook

常规设置

要启用接收Webhook:

  1. 在发布商帐户的项目中,前往项目设置 > Webhook部分。
  2. Webhook服务器字段中,指定要接收Webhook的服务器的URL,格式为https://example.com。您还可以指定在测试Web hook的工具中找到的URL。

注:

请使用HTTPS协议传输数据,不支持HTTP协议。

  1. 默认情况下会生成用于签署项目Webhook的密钥。如果想生成新的密钥,请单击刷新图标。
  2. 单击启用Webhook

Enable 
webhooks

注:

要测试Webhook,可以选择任何专用网站(例如webhook.site)或平台(例如ngrok)。

注:

无法同时将Webhook发送到不同的URL。您可以在发布商帐户中执行的操作是先指定一个用于测试的URL,然后将其替换为真实的URL。

要禁用接收Webhook:

  1. 在发布商帐户的项目中,前往项目设置 > Webhook部分。
  2. 单击禁用Webhook

高级设置

付款和商店部分的Webhook提供高级设置。单击获取Webhook按钮后,这些设置将自动显示在常规设置区块下方。

注意

如果未显示高级设置,请确保已在常规设置中连接Webhook接收,且您位于测试 > 付款和商店选项卡中。

在此部分,您可以设置在Webhook中接收额外信息。要实现此目的,请将相应开关设为启用状态。每个权限的行都会标明设置变更将影响哪些Webhook。

开关描述
显示保存的支付帐户的信息有关保存的支付方式的信息在payment_account自定义对象中传递。
显示通过保存的支付方式进行的交易的信息

信息在Webhook的以下自定义参数中传递:

  • saved_payment_method:
    • 0 — 未使用保存的支付方式
    • 1 — 进行当前付款时保存了支付方式
    • 2 — 使用了之前保存的支付方式
  • payment_type:
    • 1 — 一次性支付
    • 2 — 定期支付
将订单对象添加到Webhook有关订单的信息在支付Webhook的order对象中传递。
仅发送不含敏感数据的必要用户参数

Webhook中仅传递用户的以下信息:

  • ID
  • 国家/地区
发送自定义参数自定义令牌参数的信息在webhook中传递。
显示银行卡BIN和后缀码

Webhook中传递以下银行卡号的信息:

  • card_bin参数中的前6位数字
  • card_suffix中的后4位数字
显示银行卡品牌用于付款的银行卡的品牌。例如,Mastercard或Visa。

高级设置

在发布商帐户中测试Webhook

测试Webhook有助于确保己侧和艾克索拉侧的项目设置都正确。

如果Webhook设置成功,Webhook 设置部分下方会显示一个Webhook测试部分。

Webhook测试部分

发布商帐户中的测试部分会根据Webhook接收选项而有所不同。

如果接收合并Webhook:

Webhook测试的选项卡名称Webhook名称和类型
付款和商店用户验证 >用户验证 (user_validation)
游戏服务 > 合并Webhook >订单成功支付(order_paid)
游戏服务 > 合并Webhook >订单取消(order_canceled)
订阅用户验证 >用户验证 (user_validation)
付款 >支付(payment)

如果接收单独Webhook

Webhook测试的选项卡名称Webhook名称和类型
商店游戏服务 > 单独Webhook >订单成功支付(order_paid)
游戏服务 > 单独Webhook >订单取消(order_canceled)
付款用户验证 >用户验证 (user_validation)
付款 >支付(payment)
订阅用户验证 >用户验证 (user_validation)
付款 >支付(payment)

注:

如果测试部分出现测试未通过的警告,请在您的Webhook侦听器中检查Webhook响应设置。测试结果中指出了测试错误的原因。

示例:

您使用专门的网站webhook.site来进行测试。

测试对无效签名的响应部分显示了一个错误。

发生这种情况是因为艾克索拉发送了带有错误签名的Webhook,并期望您的处理程序用一个指出INVALID_SIGNATURE错误代码的4xx HTTP代码进行响应。

webhook.site对所有Webhook的响应中都发送一个200 HTTP代码,包括签名不正确的Webhook。由于无法获取预期的4xxHTTP代码,因此测试结果报错。

测试错误

下文将介绍合并Webhook使用场景的测试流程。

付款和商店

付款和商店选项卡中,您可以测试以下Webhook:

要进行测试:

  1. 在Webhook测试部分,前往付款和商店选项卡。

  2. 在下拉菜单中,选择商品类型。如果所选类型的商品未在发布商帐户中设置,请单击:

    • 连接 – 如果未连接该类型商品的模块
    • 配置 如果之前连接过模块,但尚未完成设置
      单击该按钮后,您将被重定向到与所选商品类型相对应的发布商帐户部分。创建商品后,返回Webhook测试部分并继续下一步
  3. 填写以下必填字段:

    1. 从下拉列表中选择商品SKU并指定金额。您可以通过单击+号并在新行中添加来选择同一类型的多个商品。
    2. 用户ID — 测试时,可以使用任意字母和数字组合。
    3. 公共用户ID — 用户可见的ID,例如电子邮件或昵称。如果在支付中心 > 设置中启用了公共用户ID,则会显示此字段。
    4. 艾克索拉订单ID字段中输入任意值。
    5. 艾克索拉发票ID — 艾克索拉侧的交易ID。测试时,可以使用任意数字值。
    6. 发票ID — 游戏侧的交易ID。测试时,可以使用任意字母和数字组合。这不是成功支付所必需的参数,但您可以通过它将游戏侧的交易ID与艾克索拉侧的交易ID关联起来。
    7. 金额 — 支付金额。测试时,可以使用任意数字值。
    8. 货币 — 从下拉列表中选择货币。
    9. 单击测试Webhook

    系统会将包含指定数据的用户验证订单成功支付订单取消Webhook发送到提供的URL。每种Webhook类型的测试结果将显示在测试Webhook按钮下方。

    如果在您的项目中启用了公共用户ID,还将看到用户搜索检查的结果。

    对于每个Webhook,您需要配置处理两种情况:成功的情况和出现错误的情况。

    付款测试部分

    订阅

    订阅选项卡中,您可以测试以下Webhook:

    注:

    在发布商帐户中,您只能测试基本的用户验证和支付Webhook。要测试其他Webhook类型,请前往:

    注:

    要测试Webhook,您应该在发布商帐户>订阅>订阅计划部分中至少有一个已创建的订阅计划

    测试步骤:

    1. 在测试部分,前往订阅选项卡。
    2. 填写以下必填字段:

    3. 用户ID — 测试时,可以使用任意字母和数字组合。
    4. 艾克索拉发票ID — 艾克索拉侧的交易ID。测试时,可以使用任意数字值。
    5. 公共用户ID — 用户可见的ID,例如电子邮件或昵称。如果在支付中心 > 设置 > 其他设置部分启用了公共用户ID,则会显示此字段。
    6. 货币 — 从下拉列表中选择货币。
    7. 计划ID — 订阅计划。从下拉列表中选择计划。
    8. 订阅产品 — 从下拉列表中选择产品(可选)。
    9. 金额 — 支付金额。测试时,可以使用任意数字值。
    10. 发票ID — 游戏侧的交易ID。测试时,可以使用任意字母和数字组合。这不是成功支付所必需的参数,但您可以通过它将游戏侧的交易ID与艾克索拉侧的交易ID关联起来。
    11. 试用期。要测试购买无试 用期的订阅或测试续订,请指定值0
    12. 单击测试Webhook
    13. 在指定的URL中,您将收到包含所填数据的Webhook。每个Webhook的测试结果(成功场景和错误场景)都在测试Webhook按钮下方显示。

      Webhook侦听器

      Webhook侦听器是一个程序代码,允许在指定URL地址接收传入的Webhook、生成签名以及发送响应到艾克索拉Webhook服务器。

      注:

      您可以使用Pay Station PHP SDK 库,它包含用于处理Webhook的现成类。

      在您的应用程序侧,实现从以下IP地址接收Webhook:

      • 185.30.20.0/24
      • 185.30.21.0/24
      • 185.30.22.0/24
      • 185.30.23.0/24
      • 34.102.38.178
      • 34.94.43.207
      • 35.236.73.234
      • 34.94.69.44
      • 34.102.22.197

      如集成了登录管理器 产品,请另外添加对来自以下IP地址的Webhook的处理:

      • 34.94.0.85
      • 34.94.14.95
      • 34.94.25.33
      • 34.94.115.185
      • 34.94.154.26
      • 34.94.173.132
      • 34.102.48.30
      • 35.235.99.248
      • 35.236.32.131
      • 35.236.35.100
      • 35.236.117.164

      限制:

      • 应用程序的数据库中不应存在具有相同ID的多个成功交易。
      • 如果Webhook侦听器收到的Webhook的ID已经存在于数据库中,则需返回之前处理该交易的结果。不建议向用户发放重复购买及在数据库中创建重复记录。

      生成签名

      为确保数据传输安全,您必须验证Webhook确实来自艾克索拉服务器,且在传输过程中未被篡改。为此,需要基于请求正文负载生成您自己的签名,并将其与传入请求的au thorization标头中提供的签名进行比较。如果签名匹配,则Webhook是真实的,可以安全处理。

      验证步骤:

      1. 从Webhook请求的authorization标头中检索签名。标头格式为Signature <signature_value>

      2. 检索JSON格式的Webhook请求正文。

        注意

        请完全按照接收到的JSON负载使用。不要解析或重新编码负载,因为这会改变 格式并导致签名验证失败。

      3. 生成您自己的签名进行比较:

        1. 通过将密钥附加到字符串末尾,将JSON负载与您项目的密钥连接起来。

      4. 对结果字符串应用SHA-1加密哈希函数。结果将是小写十六进制字符串。
      5. 将您生成的签名与authorization标头中的签名进行比较。如果匹配,则Webhook是真实的。

      以下是C#、C++、Go、PHP和Node.js语言的签名生成实现示例。

      Webhook示例(HTTP):

      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@example.com",
            "id":1234567,
            "name":"Xsolla User",
            "country":"US"
        }
      }

      Webhook示例(curl):

      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@example.com",
              "id": 1234567,
              "name": "Xsolla User",
              "country": "US"
            }
          }'

      C#签名生成实现示例(一般示例):

      注意

      此代码示例兼容.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;
          }
      }

      C#签名生成实现示例(适用于.NET 5.0及更高版本):

      注意

      使用 Convert.ToHexString方法需要.NET 5.0或更高版本。

      若您使用.NET 7.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;
          }
      }

      C#签名生成实现示例(适用于.NET 7.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);
      }

      C++签名生成实现示例:

      #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;
          }
      };

      Go签名生成实现示例:

      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签名生成实现示例:

      <?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));
          }
      }
      ?>

      Node.js签名生成实现示例:

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

      向Webhook发送响应

      要确认收到Webhook,您的服务器必须返回:

      • 如果响应成功,返回200201204HTTP代码。
      • 如果未找到指定的用户或传递了无效的签名,返回400 HTTP代码和问题描述。如果您的服务器出现临时问题,您的Webhook处理程序还可 以返回5xxHTTP 代码。

      如果艾克索拉服务器未收到订单成功支付订单取消Webhook的响应,或收到5xx代码的响应,系统将按以下计划重新发送Webhook:

      • 尝试2次,间隔5分钟
      • 尝试7次,间隔15分钟
      • 尝试10次,间隔60分钟

      在首次尝试后的12小时内最多尝试发送20次Webhook。

      支付退款Webhook的重试逻辑说明见相应的Webhook页面。

      注意

      如满足以下所有条件,款项仍将退还给用户:

      • 退款由艾克索拉发起。
      • Webhook响应返回4xx状态码,或在所有重试后未收到响应,或返回5xx状态码。

      如果艾克索拉服务器未收到用户验证Webhook的响应,或收到4005xx代码的响应,则不会重新发送用户验证Webhook。在这种情况下,用户会看到错误提示,且系统不会发送支付订单成功支付Webhook。

      错误

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

      Webhook列表

      注:

      通知类型在notification_type参数中发送。

      Webhook通知类型描述
      用户验证user_validation发送以检查用户是否存在于游戏中。
      用户搜索user_search发送以根据公共用户ID获取用户信息。
      支付payment用户完成支付流程时发送。
      退款refund出于某些原因需要取消支付时发送。
      部分退款partial_refund出于某些原因需要部分取消支付时发送。
      付款被拒ps_declined当付款被支付系统拒绝时发送。
      AFS拒绝交易afs_reject交易在AFS检查过程中被拒绝时发送。
      AFS更新的拦截列表afs_black_listAFS拦截列表发生更新时发送。
      创建了订阅create_subscription用户创建订阅时发送。
      更新了订阅update_subscription订阅发生续订或更改时发送。
      取消了订阅cancel_subscription取消订阅时发送。
      非续订订阅non_renewal_subscription状态设置为非续订时发送。
      添加支付账户payment_account_add当用户添加或保存支付帐户时发送。
      删除支付账户payment_account_remove用户从已保存的帐户中删除了支付帐户时发送。
      Web商店中的用户验证-从Web商店网站发送以检查游戏中是否存在该用户。
      合作伙伴侧目录个性化partner_side_catalog用户与商店交互时发送。
      订单成功支付order_paid订单付款后发送。
      订单取消order_canceled订单取消时发送。
      争议dispute当提出新争议时发送。
      下载 OpenAPI 描述
      语言
      服务器
      Mock server
      https://xsolla.redocly.app/_mock/zh/webhooks/
      https://api.xsolla.com/merchant/v2/
      Webhook
      Webhook

      请求

      当用户完成付款时,艾克索拉会向Webhook URL发送一个包含付款详细信息的payment类型的Webhook。

      预期响应代码的说明见Responses 部分,您也可以使用其他响应代码:

      响应代码描述
      200201204成功的响应。
      4xx发生错误。例如,如果未找到指定的用户或传递了无效的签名。
      5xx服务器临时错误。收到此响应后,艾克索拉将自动重试发送Webhook,并逐渐增加尝试间隔,直到监听器确认接收。48小时内最多可重试12次。

      发布商帐户中保 存Webhook URL时,还可以设置在Webhook中接收额外信息。

      注:

      如果是在2025年1月22日或之前注册的发布商帐户,可在项目的设置> Webhooks> 测试 > 付款> 高级设置部分中找到这些开关。

      开关描述
      显示保存的支付帐户的信息有关保存的支付方式的信息在payment_account自定义对象中传递。
      显示通过保存的支付方式进行的交易的信息

      信息在Webhook的以下自定义参数中传递:

      • saved_payment_method:
        • 0 — 未使用保存的支付方式
        • 1 — 进行当前付款时保存了支付方式
        • 2 — 使用了之前保存的支付方式
      • payment_type:
        • 1 — 一次性支付
        • 2 — 定期支付
      将订单对象添加到Webhook有关订单的信息在支付Webhook的order对象中传递。
      仅发送不含敏感数据的必要用户参数

      Webhook中仅传递用户的以下信息:

      • ID
      • 国家/地区
      显示银行卡BIN和后缀码

      Webhook中传递以下银行卡号的信息:

      • card_bin参数中的前6位数字
      • card_suffix中的后4位数字
      显示银行卡品牌用于付款的银行卡的品牌。例如,Mastercard或Visa。

      注意

      Webhook中发送的字段集取决于:

      • 在发布商帐户中配置的高级设置
      • 在艾克索拉侧配置的自定义设置

      如果您有任何疑问,请联系您的客户成功经理或发送电子邮件至csm@xsolla.com

      正文application/json
      custom_parametersobject

      您的自定义参数。

      notification_typestring(notification_type)必需

      通知类型。

      payment_detailsobject必需

      带有支付详细信息的对象。

      payment_details.​country_whtobject

      跨境交易在特定国家/地区收取的预扣税(对象)。

      payment_details.​direct_whtobject

      直接预扣税。

      payment_details.​paymentobject

      带有用户支付相关数据的对象。

      payment_details.​payment_method_feeobject

      支付系统佣金的大小。

      payment_details.​payment_method_sumobject

      带有通过支付方式收费的金额相关数据的对象。

      payment_details.​payoutobject

      带有支出详细信息的对象。

      payment_details.​payout_currency_ratestring

      从支付币种到支出币种的汇率。

      payment_details.​repatriation_commissionobject

      包含汇回本国费用数据的对象,该费用是第三方对艾克索拉收取的费用。

      payment_details.​sales_taxobject

      销售税(对象;仅适用于美国和加拿大)。

      payment_details.​user_acquisition_feeobject

      从通过联盟网络和圈内达人完成的购买中扣除的用户获取费用总额(对象)。

      payment_details.​vatobject

      增值税大小(仅适用于欧盟)。

      payment_details.​xsolla_balance_sumobject

      计入艾克索拉余额的金额。

      payment_details.​xsolla_feeobject

      艾克索拉费用(对象)。

      purchaseobject

      带有购买相关数据的对象。

      settingsobject

      带有自定义项目设置的对象。

      transactionobject必需

      交易ID。

      transaction.​agreementinteger

      协议ID。

      transaction.​dry_runinteger

      测试交易。如为测试交易,该参数的值为1;如为真实交易,则不会发送该参数。

      transaction.​external_idstring(external-id)

      交易外部ID。详细信息请参阅常见问答

      transaction.​idinteger

      交易ID。

      transaction.​payment_datestring

      付款日期。

      transaction.​payment_methodinteger

      付款方式ID。

      transaction.​payment_method_namestring

      付款方式名称。

      transaction.​payment_method_order_idstring

      支付系统中的付款ID。

      userobject

      用户详情(对象)。

      curl -v 'https://your.hostname/your/uri' \
      -X POST \
      -d '{
          "notification_type": "payment",
          "settings": {
            "project_id": 18404,
            "merchant_id": 2340
          },
          "purchase": {
              "subscription": {
                  "plan_id": "b5dac9c8",
                  "subscription_id": "10",
                  "product_id": "Demo Product",
                  "date_create": "2014-09-22T19:25:25+04:00",
                  "date_next_charge": "2014-10-22T19:25:25+04:00",
                  "currency": "USD",
                  "amount": 9.99
              },
              "checkout": {
                  "currency": "USD",
                  "amount": 50
              },
              "total": {
                  "currency": "USD",
                  "amount": 200
              },
              "promotions": [{
                  "technical_name": "Demo Promotion",
                  "id": 853
              }],
              "coupon": {
                  "coupon_code": "ICvj45S4FUOyy",
                  "campaign_code": "1507"
              },
              "order": {
                "id": 1234
                "lineitems": [
                {
                  "sku": "com.xsolla.item_1",
                  "quantity": 1,
                  "price": {
                    "currency": "EUR",
                    "amount": 6.5
                    }
                }
                ]
                }
          },
          "user": {
              "ip": "127.0.0.1",
              "phone": "18777976552",
              "email": "email@example.com",
              "id": "1234567",
              "name": "John Smith",
              "country": "US"
          },
          "transaction": {
              "id": 1,
              "external_id": 1,
              "payment_date": "2014-09-24T20:38:16+04:00",
              "payment_method": 1,
              "payment_method_name": "PayPal",
              "payment_method_order_id": 1234567890123456789,
              "dry_run": 1,
              "agreement": 1
          },
          "payment_details": {
              "payment": {
                  "currency": "USD",
                  "amount": 230
              },
              "vat": {
                  "currency": "USD",
                  "amount": 0,
                  "percent": 20
              },
              "sales_tax": {
                  "currency": "USD",
                  "amount": 0,
                  "percent": 0
              },
              "direct_wht": {
                  "currency": "USD",
                  "amount": 0,
                  "percent": 0
              },
              "payout_currency_rate": "1",
              "payout": {
                  "currency": "USD",
                  "amount": 200
              },
              "country_wht": {
                  "currency": "USD",
                  "amount": 2,
                  "percent": 10
              },
              "user_acquisition_fee": {
                  "currency": "USD",
                  "amount": 2,
                  "percent": 1
              },
              "xsolla_fee": {
                  "currency": "USD",
                  "amount": 10
              },
              "payment_method_fee": {
                  "currency": "USD",
                  "amount": 20
              },
              "repatriation_commission": {
                  "currency": "USD",
                  "amount": 10
              }
          },
          "custom_parameters": {
              "parameter1": "value1",
              "parameter2": "value2"
          }
      }'

      响应

      返回以指示处理成功。

      响应
      无内容

      请求

      当支付系统拒绝交易时,艾克索拉会将交易详情通过ps_declined类型的Webhook发送至您配置的Webhook URL。该Webhook在授权或支付处理阶段发送。此情况下,不会发送payment\ order_paid Webhook。

      支付系统拒绝的常见原因:

      • 卡片授权失败(例如,支付系统因技术错误或银行无响应而无法完成授权流程)或被拒(例如,银行已响应但因资金不足或卡片信息无效而拒绝交易)。
      • 3-D Secure验证失败、未完成或用户确认超时。
      • 处理方或收单银行暂时不可用,或因不可逆转的错误(如账户已关闭或卡号无效)而强制拒绝。在不解决根本问题的情况下重试将无法成功完成交易。

      不应与以下情况混淆:

      • 反欺诈拒绝,这类情况通过afs_reject Webhook报告。
      • 成功支付后的退款和部分退款,这类情况通过refundpartial_refund webhook报告。

      注意

      如需接收ps_declined Webhook,请联系您的客户成功经理或发送邮件至csm@xsolla.com

      正文application/json
      notification_typestring(notification_type)必需

      通知类型。

      refund_detailsobject

      退款详情(对象)。

      settingsobject

      带有自定义项目设置的对象。

      transactionobject必需

      交易ID。

      transaction.​dry_runinteger

      测试交易。如为测试交易,该参数的值为1;如为真实交易,则不会发送该参数。

      transaction.​external_idstring

      交易外部ID。

      transaction.​idinteger

      交易ID。

      transaction.​payment_methodinteger

      付款方式ID。

      userobject

      用户详情(对象)。

      curl -v 'https://your.hostname/your/uri' \
      -X POST \
      -H 'Accept: application/json' \
      -H 'Content-Type: application/json' \
      -H 'Authorization: Signature 80543ba63e1e50cf05f15150fe75e7245da9a898' \
      -d '{
        "notification_type": "ps_declined",
        "settings": {
          "project_id": "18404",
          "merchant_id": "2340"
        },
        "user": {
          "ip": "127.0.0.1",
          "email": "email@example.com",
          "id": "1234567",
          "country": "US"
        },
        "transaction": {
          "id": "1",
          "dry_run": "1",
          "payment_method": "1"
        },
        "refund_details": {
          "author": "support@xsolla.com",
          "code": "8",
          "reason": "Cancellation by the PS request",
          "reason_detail": "Insufficient funds"
        }
      }'

      响应

      返回以指示处理成功。

      响应
      无内容

      请求

      当支付取消时,艾克索拉会向Webhook URL发送带有refund类型的Webhook,其中包含已取消交易的详细信息。

      Webhook重试机制取决于退款发起方:

      • 若退款由您方发起,系统不会重新发送Webhook。无论对Webhook的响应如何,款项都将退还给用户。
      • 若退款由第三方发起(如支付系统或艾克索拉客户支持团队),且Webhook响应返回5xx状态码,系统会按递增间隔重新发送Webhook。最多重试12次,时间范 围为首次尝试后的48小时内。

      有关退款流程的详细信息,请参阅相关说明

      注意

      如满足以下所有条件,款项仍将退还给用户:

      • 退款由艾克索拉发起。
      • Webhook响应返回4xx状态码,或在所有重试后未收到响应,或返回5xx状态码。

      发布商帐户中保 存Webhook URL时,还可以设置在Webhook中接收额外信息。

      注:

      如果是在2025年1月22日或之前注册的发布商帐户,可在项目的设置> Webhooks> 测试 > 付款> 高级设置部分中找到这些开关。

      开关描述
      显示通过保存的支付方式进行的交易的信息

      信息在Webhook的以下自定义参数中传递:

      • saved_payment_method:
        • 0 — 未使用保存的支付方式
        • 1 — 进行当前付款时保存了支付方式
        • 2 — 使用了之前保存的支付方式
      • payment_type:
        • 1 — 一次性支付
        • 2 — 定期支付

      退款代码:

      代码退款理由描述
      1Cancellation by the user request / the game request用于在发布商帐户中发起取消的情况。
      2Chargeback交易存在退单的情况。
      3Integration error用于艾克索拉与游戏之间存在集成问题的情况。
      这种情况下,我们不建议将用户列入黑名单。
      4Potential fraud存在欺诈嫌疑。
      建议:将该用户添加到黑名单。
      5Test payment用于测试交易然后取消的情况。
      这种情况下,我们不建议将用户列入黑名单。
      6User invoice expired用于通过用后付费模式的支付系统进行交易的情况。
      7Fraud notification from PS支付被支付系统拒绝。 支付系统检测到潜在欺诈行为。
      建议:将该用户添加到黑名单。
      8Cancellation by the PS request用于支付系统请求取消的情况。
      这种情况下,我们不建议将用户列入黑名单。
      9Cancellation by the user request用于用户请求取消的情况。可能出于某些原因导致用户对游戏或购买产生不满的情况下发生。
      这种情况下,我们不建议将用户列入黑名单。
      10Cancellation by the game request用于游戏请求取消的情况。
      这种情况下,我们不建议将用户列入黑名单。
      11Account holder called to report fraud用于账户持有人通知我们其未进行此交易的情况。
      12Friendly fraud用于接收到友好型欺诈相关消息的情况。
      13Duplicate有同一张发票的重复交易。
      正文application/json
      custom_parametersobject

      您的自定义参数。

      notification_typestring(notification_type)必需

      通知类型。

      payment_detailsobject必需

      带有支付详细信息的对象。

      payment_details.​country_whtobject

      跨境交易在特定国家/地区收取的预扣税(对象)。

      payment_details.​direct_whtobject

      直接预扣税。

      payment_details.​paymentobject

      带有用户支付相关数据的对象。

      payment_details.​payment_method_feeobject

      支付系统佣金的大小。

      payment_details.​payment_method_sumobject

      带有通过支付方式收费的金额相关数据的对象。

      payment_details.​payoutobject

      带有支出详细信息的对象。

      payment_details.​payout_currency_ratestring

      从支付币种到支出币种的汇率。

      payment_details.​repatriation_commissionobject

      包含汇回本国费用数据的对象,该费用是第三方对艾克索拉收取的费用。

      payment_details.​sales_taxobject

      销售税(对象;仅适用于美国和加拿大)。

      payment_details.​user_acquisition_feeobject

      从通过联盟网络和圈内达人完成的购买中扣除的用户获取费用总额(对象)。

      payment_details.​vatobject

      增值税大小(仅适用于欧盟)。

      payment_details.​xsolla_balance_sumobject

      计入艾克索拉余额的金额。

      payment_details.​xsolla_feeobject

      艾克索拉费用(对象)。

      purchaseobject

      带有购买相关数据的对象。

      refund_detailsobject

      退款详情(对象)。

      settingsobject

      带有自定义项目设置的对象。

      transactionobject必需

      交易ID。

      transaction.​agreementinteger

      协议ID。

      transaction.​dry_runinteger

      测试交易。如为测试交易,该参数的值为1;如为真实交易,则不会发送该参数。

      transaction.​external_idstring

      交易外部ID。

      transaction.​idinteger

      交易ID。

      transaction.​payment_method_order_idstring

      支付系统中的付款ID。

      userobject

      用户详情(对象)。

      curl -v 'https://your.hostname/your/uri' \
      -X POST \
      -d '{
              "notification_type": "refund",
              "settings": {
                "project_id": 18404,
                "merchant_id": 2340
              },
              "purchase": {
                  "subscription": {
                      "plan_id": "b5dac9c8",
                      "subscription_id": "10",
                      "date_create": "2014-09-22T19:25:25+04:00",
                      "currency": "USD",
                      "amount": 9.99
                  },
                  "checkout": {
                      "currency": "USD",
                      "amount": 50
                  },
                  "total":{
                      "currency": "USD",
                      "amount": 200
                  }
              },
              "user": {
                  "ip": "127.0.0.1",
                  "phone": "18777976552",
                  "email": "email@example.com",
                  "id": "1234567",
                  "name": "John Smith",
                  "country": "US"
              },
              "transaction": {
                  "id": 1,
                  "external_id": 1,
                  "dry_run": 1,
                  "agreement": 1
              },
              "refund_details": {
                  "code": 4,
                  "reason": "Potential fraud"
              },
              "payment_details": {
                  "sales_tax": {
                      "currency": "USD",
                      "amount": 0
                  },
                  "direct_wht": {
                      "currency": "USD",
                      "amount": 0.70
                  },
                  "xsolla_fee": {
                      "currency": "USD",
                      "amount": "10"
                  },
                  "payout": {
                      "currency": "USD",
                      "amount": "200"
                  },
                  "payment_method_fee": {
                      "currency": "USD",
                      "amount": "20"
                  },
                  "payment": {
                      "currency": "USD",
                      "amount": "230"
                  },
                  "repatriation_commission": {
                      "currency": "USD",
                      "amount": 10
                  }
              }
          }
      }'

      响应

      返回以指示处理成功。

      响应
      无内容
      Webhook
      Webhook
      Webhook
      Webhook
      Webhook