Market NinjaMarket Ninja
使用场景

Webhook

解析会话完成时的异步通知,无需轮询 API——订阅方式、投递验证,以及一个 n8n 集成示例。

与其不断轮询 GET /me/scrapes/{id} 等待 status 变为 completed,不如订阅一个 URL, 我们会在完成的那一刻主动向它发送 POST 请求。目前存在一种事件:scrape_session.completed

订阅的创建与管理都在扩展程序的设置页面完成(Webhook 部分),而不是通过本 API——并没有可供您自行调用的 POST /webhooks 端点。每个账户最多可拥有 5 个有效订阅。

事件负载是一个指针,而不是数据本身

{
  "event": "scrape_session.completed",
  "user_id": "3e2f9a10-df34-4b8a-9c31-8e1d2f6a7b90",
  "scrape_session_id": "b1f8b6b0-2f7d-4a1a-9a3c-2f6d8e9a1234",
  "status": "completed",
  "stats": { "total": 40, "pending": 0, "processing": 0, "merged": 38, "rejected": 1, "flagged": 1 },
  "url": "https://data.marketninja.ru/v1/me/scrapes/b1f8b6b0-2f7d-4a1a-9a3c-2f6d8e9a1234/products"
}

我们特意不把解析到的数据直接嵌入事件本身——要获取数据,请请求 url 字段中的地址 (授权方式与本站其他所有接口相同,都使用 Bearer 认证,且这次调用会计入您平常的每日配额, 与直接调用该端点完全一样)。这样设计有两个原因:每日数据配额只会在这一个端点上被计算, 并且这种精简的负载能保证同一个 webhook 无论会话中有 1 件商品还是 10,000 件商品, 处理起来都是一样的。

user_id 用于标识该事件属于您哪一个 Market Ninja 账户——如果您曾把多个账户的 webhook 指向同一个 URL(例如一个共用的自动化入口),这个字段可以帮助您区分它们。

验证投递

每个请求都携带两个请求头:

  • X-Marketninja-Event: scrape_session.completed
  • X-Marketninja-Signature: t=1735689600,v1=5257a869e7bcd1d2...

签名是基于 ${timestamp}.${rawBody} 计算的 HMAC-SHA256,使用创建订阅时仅显示一次的 签名密钥。请务必用收到的原始请求体字符串进行校验——绝不要用重新序列化后的 JSON.stringify(JSON.parse(body)) 来比对:这无法保证字节级一致(字段顺序、空白字符等), 会导致校验悄无声息地失败。

import crypto from "node:crypto";

function isValidSignature(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((p) => p.split("="))
  );
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(parts.v1),
    Buffer.from(expected)
  );
}
如果您的端点在收到事件后会执行一些敏感操作,建议进行签名验证,但这不是强制要求。如果还需要防重放攻击,也请检查 t 是否过旧(通常几分钟的容差就足够)——我们这一侧不会对时间戳做任何容差限制。

投递、重试与放弃时机

  • 任何 2xx 响应都会被视为投递成功——响应体不会被读取。
  • 我们不会跟随重定向——3xx 会被当作明确的失败处理。
  • 每次尝试的超时时间为 5 秒。
  • 失败的尝试(网络错误、超时、4295xx)会按退避计划重试:大致间隔为 1 分钟、 5 分钟、30 分钟、2 小时、6 小时,最后再等 24 小时——最多重试 7 次,总跨度约为一天半—— 之后这一次具体的投递会被标记为永久失败。如果 429 响应带有 Retry-After 请求头, 会优先按该值执行,而不是固定计划。
  • 任何其他非 2xx 响应(例如 400404)会立即被视为永久失败——我们默认重试不会 改变您的端点给出的判断。
  • 如果某个订阅累计了足够多次连续失败,它会被自动禁用——被禁用的订阅无法重新启用, 只能在扩展程序的设置页面重新创建一个新订阅。
您的端点必须能通过公开的 https:// 地址访问——如果 target_url 解析为私有地址、回环地址或链路本地地址,我们会在创建订阅时就拒绝它(并且每次投递前都会重新检查)。如果要测试本地服务器,需要先搭建一个隧道(如 ngrok、Cloudflare Tunnel 等)。

示例:在 n8n 中触发 LLM 价格分析

一个常见的用法:收到通知、取出数据、交给 LLM 处理、再把结果发送到某处。整个流程中, 只有两个节点是 Market Ninja 特有的;之后的一切完全由您自行决定。

  1. 一个 Webhook 节点作为触发器——HTTP Method: POST,路径可任意选择。这就是您在 注册订阅时要填入的目标 URL。

  2. 紧接其后的一个 HTTP Request 节点,配置为对事件中的 url 字段发起 GET 请求, 并使用 Bearer 认证:

    • URL:{{ $json.body.url }}
    • Authentication:Generic Credential Type → Bearer Auth,凭证中保存您的某个 mn_live_... API 密钥。

    其输出格式为 { data: [...], meta: {...} }——与 GET /me/scrapes/{id}/products 始终返回的格式完全一致:每条提交记录一项,product 字段在尚未合并时为 null

  3. 接下来,您可以对 $json.data 做任何真正需要的处理——例如用 LLM 节点生成价格趋势摘要、 发送 Slack/Telegram 通知、写入数据库,或者把这几步串联起来。这部分完全与 Market Ninja 无关,按普通 n8n 工作流的方式搭建即可。

n8n 的 Webhook 节点默认会立即返回 200,无论后续工作流是否成功执行。我们这一侧显示的「已投递」状态,只能确认您的 Webhook 节点收到了通知——自动化本身是否成功执行,请以您自己工作流的执行历史为准。

Last updated on

On this page