You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js/React Shopify应用外部Webhook负载处理及API调用问题

解决方案:外部Webhook接收+Shopify API安全调用协调

核心问题分析

你遇到的401错误本质是加载的离线Session令牌可能已失效,同时直接暴露的Webhook端点存在安全风险。要同时满足两个需求,需做到:

  • 验证外部Webhook请求的合法性,防止伪造
  • 确保调用Shopify API的离线Session令牌有效

具体实现步骤

1. 给外部Webhook添加安全验证

必须先验证外部Webhook的身份,避免恶意请求。假设外部API通过X-Webhook-Signature请求头传递HMAC签名,实现验证逻辑:

import crypto from 'crypto';

// 验证外部Webhook签名的工具函数
function validateExternalWebhook(req, secretKey) {
  const signature = req.headers['x-webhook-signature'];
  if (!signature) return false;
  
  const hmac = crypto.createHmac('sha256', secretKey)
    .update(JSON.stringify(req.body))
    .digest('hex');
  
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hmac));
}

2. 修复离线Session的API调用问题

离线Session的access token可能过期,需添加令牌刷新逻辑。修改你的/delivery端点:

// 替换原/delivery端点代码
app.post("/delivery", async (req, res) => {
  try {
    // 步骤1:验证外部Webhook签名(替换为你的外部API密钥)
    const WEBHOOK_SECRET = process.env.EXTERNAL_WEBHOOK_SECRET;
    if (!validateExternalWebhook(req, WEBHOOK_SECRET)) {
      return res.status(403).send({ error: "无效的Webhook签名" });
    }

    const { shop } = req.query;
    if (!shop) {
      return res.status(400).send({ error: "Shop域名必填" });
    }

    // 步骤2:加载离线Session
    const sessionId = await shopify.api.session.getOfflineId(shop);
    let session = await shopify.config.sessionStorage.loadSession(sessionId);
    if (!session) {
      return res.status(404).send({ error: "未找到该店铺的离线Session" });
    }

    // 步骤3:调用Shopify API,遇401则刷新令牌重试
    let client = new shopify.api.clients.Graphql({ session });
    let orderData;
    try {
      orderData = await client.request(`
        query shopifyOrders {
          orders(first: 5) {
            edges {
              node {
                id
                name
                totalPrice
              }
            }
          }
        }
      `);
    } catch (error) {
      if (error.message.includes("401 Unauthorized")) {
        try {
          // 刷新离线Session令牌
          const refreshedSession = await shopify.api.auth.refreshSession(session);
          await shopify.config.sessionStorage.storeSession(refreshedSession);
          session = refreshedSession;
          // 重新创建客户端并请求
          client = new shopify.api.clients.Graphql({ session });
          orderData = await client.request(`
            query shopifyOrders {
              orders(first: 5) {
                edges {
                  node {
                    id
                    name
                    totalPrice
                  }
                }
              }
            }
          `);
        } catch (refreshError) {
          console.error("Session刷新失败", refreshError);
          return res.status(500).send({ error: "无法获取有效访问令牌" });
        }
      } else {
        throw error;
      }
    }

    // 步骤4:根据外部Webhook负载更新订单配送状态
    const { orderId, deliveryStatus } = req.body;
    const updateResult = await client.request(`
      mutation updateOrderFulfillment($orderId: ID!, $status: FulfillmentStatus!) {
        orderUpdate(input: {id: $orderId, fulfillmentStatus: $status}) {
          order {
            id
            fulfillmentStatus
          }
        }
      }
    `, {
      variables: {
        orderId: orderId, // 需传入Shopify标准gid格式ID,如gid://shopify/Order/123456789
        status: deliveryStatus.toUpperCase() // 枚举值:FULFILLED/PARTIALLY_FULFILLED/UNFULFILLED
      }
    });

    res.status(200).send({ 
      orders: orderData.data.orders.edges,
      updatedOrder: updateResult.data.orderUpdate.order
    });
  } catch (error) {
    console.error("Webhook处理失败", error);
    res.status(500).send({ error: "服务器内部错误" });
  }
});

3. 环境变量配置

在.env文件中添加外部Webhook密钥:

EXTERNAL_WEBHOOK_SECRET=你的外部API签名密钥

替代方案:后台作业队列处理

若Webhook处理逻辑复杂或耗时,可将任务放入后台队列避免同步超时:

  • 接收并验证Webhook后,将任务(Shop域名、订单ID、配送状态)存入队列
  • 后台Worker从队列取任务,加载Session并调用Shopify API
  • 适合高并发或耗时操作,可使用@shopify/shopify-app-express自带的jobQueue或第三方库如BullMQ

内容的提问来源于stack exchange,提问作者beef nachos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 10:44:52