跳转至

Webhooks 集成指南

当特定的礼品卡事件发生时,Gift Card Hero 会向你的服务器发送 HTTP POST 请求。使用 Webhooks 可以实时让外部系统与礼品卡活动保持同步。


设置

  1. 进入左侧导航中的 For developers
  2. 勾选 Enable REST API 并保存。只有在 REST API 开启时,Webhook 设置才会显示
  3. 点击 Generate Secret,生成 Webhook 签名密钥

生成 Webhook 密钥

密钥只会显示一次

请复制密钥并妥善保管。如果丢失,请点击 Regenerate Secret 生成新密钥。旧密钥会立即失效,因此也需要同时更新你的服务器。

  1. 为每个需要接收的 Webhook 类型输入端点 URL,然后点击保存栏中的 Save

Webhook 端点 URL

新增或修改的端点 URL 必须以 https:// 开头,并指向可从互联网访问的服务器。私有网络中的地址(例如 localhost、127.0.0.1、10.x.x.x、192.168.x.x 等)在保存时会被拒绝。在此规则出台之前保存的 URL 会继续工作,直到你修改它为止,但只有在它指向公共地址时才会被调用。要对本地服务器进行测试,请通过 ngrok 等隧道将其暴露出来,并使用其提供的公开 https URL。


事件类型

Gift Card Hero 有三个 Webhook URL 字段:

事件 Topic 请求头的值 触发时机
Gift card order gift_card_order Gift Card Hero 处理订单中的礼品卡。负载列出属于该订单的卡
Gift card payment gift_card_payment 创建了一个全部或部分使用礼品卡付款的订单
Gift card balance change gift_card_balance 目前不会发送

余额变动 Webhook

页面上有 Gift card balance webhook URL 字段,但 Gift Card Hero 目前不会发送此事件。目前若要跟踪余额变动,请轮询 GET /gift-cards/transactions,或对付款使用 gift_card_payment 事件。

只有在 Enable REST API 开启且该事件的 URL 已填写时,才会发送 Webhook。


投递

  • 方法: POST
  • 超时: 6 秒
  • 尝试次数: 总共最多 5 次投递尝试(首次尝试加最多 4 次重试),延迟呈指数增长,初始约为 30 到 60 秒
  • 重定向: 不会跟随。3xx 响应被视为投递失败,因此请使用端点的最终 URL。

如果你的端点在 6 秒内没有返回 2xx 响应,则视为投递失败,并会重试。


请求头

每个 Webhook 请求都包含以下请求头:

请求头 值
Content-Type application/json
User-Agent GiftHero-Webhooks/1.0
X-GiftHero-Topic 事件类型(例如 gift_card_order)
X-GiftHero-Signature 用于验证的 HMAC-SHA256 签名。仅在你生成了 Webhook 密钥时发送

签名验证

X-GiftHero-Signature 请求头包含一个 HMAC-SHA256 签名,采用十六进制编码,并以 sha256= 为前缀。

重要: 签名是针对负载正文中仅包含 { type, timestamp } 的 JSON 对象计算的,而不是整个请求正文。

验证步骤

  1. 解析请求正文,提取 type 和 timestamp 字段
  2. 构造签名负载:JSON.stringify({ type, timestamp })
  3. 使用你的 Webhook 密钥作为密钥,计算 HMAC-SHA256
  4. 将结果进行十六进制编码,并以 sha256= 为前缀
  5. 使用恒定时间比较,将其与 X-GiftHero-Signature 请求头进行比较

示例(Node.js)

const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const signingPayload = JSON.stringify({
    type: body.type,
    timestamp: body.timestamp
  });

  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(signingPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

负载结构

每个请求正文都具有相同的外层结构。事件详情位于 data 中:

{
  "type": "gift_card_order",
  "timestamp": "2026-03-15T10:30:00.000Z",
  "data": { }
}

礼品卡订单(gift_card_order)

{
  "type": "gift_card_order",
  "timestamp": "2026-03-15T10:30:00.000Z",
  "data": {
    "order": {
      "id": 5551234567890,
      "name": "#1042",
      "email": "buyer@example.com",
      "createdAt": "2026-03-15T10:30:00.000Z",
      "lineItems": [
        {
          "id": 11198765432100,
          "title": "Gift Card",
          "quantity": 1,
          "sku": "GC-50",
          "variantId": 44556677
        }
      ]
    },
    "giftCards": [
      {
        "id": 987654321,
        "balance": "50.00",
        "currency": "USD",
        "lastCharacters": "ab12",
        "initialValue": "50.00",
        "customer": { "id": 123, "email": "buyer@example.com", "name": "Alex Buyer" },
        "recipient": { "id": 456, "email": "friend@example.com", "name": "Sam Friend" },
        "code": "abcd1234efgh5678",
        "lineItemId": 11198765432100,
        "sku": "GC-50"
      }
    ]
  }
}

礼品卡付款(gift_card_payment)

giftCards 中的每一项还会携带为该订单付款的 transaction。

{
  "type": "gift_card_payment",
  "timestamp": "2026-03-16T14:22:00.000Z",
  "data": {
    "order": {
      "id": 5551234567891,
      "name": "#1043",
      "email": "shopper@example.com",
      "createdAt": "2026-03-16T14:22:00.000Z",
      "lineItems": [
        { "id": 11198765432200, "title": "Running Shoes", "quantity": 1, "sku": "SHOE-9" }
      ]
    },
    "giftCards": [
      {
        "id": 987654321,
        "balance": "0.00",
        "currency": "USD",
        "lastCharacters": "ab12",
        "initialValue": "50.00",
        "customer": null,
        "recipient": null,
        "transaction": {
          "id": "gid://shopify/OrderTransaction/123456789",
          "amount": "50.00",
          "currency": "USD",
          "type": "debit",
          "createdAt": "2026-03-16T14:22:00.000Z"
        }
      }
    ]
  }
}

字段说明

  • data.order 和 data.giftCards 是你应当依赖的字段。data 顶层还会重复部分订单字段(orderId、orderName、orderEmail、createdAt、lineItems),并包含内部字段(storeId、shop、jobType)。请不要依赖这些字段。
  • balance 是构建 Webhook 时卡的余额,而不是下单时的余额。
  • code 是完整的礼品卡代码。仅当 Gift Card Hero 有该代码记录时才会包含,否则省略。请将其视为密码,并确保你的端点安全。
  • 当卡可以与订单行匹配时,会包含 lineItemId 和 sku。
  • lineItems 条目包含 id、title、quantity、sku 和 variantId(不含价格)。
  • 如果无法加载某张卡的详细信息,其条目会缩减为 id、零金额以及空的 lastCharacters。遇到这种情况时,请通过 REST API 重新读取该卡。

最佳实践

  • 尽快返回 2xx:立即确认收到 Webhook,并异步处理负载。超过 6 秒的响应会被视为失败。
  • 处理重复项:由于重试,同一事件可能被多次投递。请使用事件数据中的订单和礼品卡 ID 进行去重。timestamp 是每次尝试的时间,因此不能作为稳定的键。
  • 验证 HMAC 签名:在处理任何 Webhook 负载之前,务必验证 X-GiftHero-Signature 请求头。拒绝签名无效的请求。
  • 使用幂等键:将 Webhook 处理程序设计为幂等的,使同一事件处理两次的结果与处理一次相同。
  • 监控失败:如果你的端点持续失败,Gift Card Hero 将在总共 5 次尝试后停止重试。请定期检查服务器日志和端点健康状况。