Integrations

Webhook

把 Surpasso 文章发送到 Make、Zapier、n8n、Lovable 或自建网站。

如果网站不属于现成的发布平台,可以使用 Webhook。每次发送文章时,Surpasso 会把 完整文章作为一个安全的 HTTPS 请求发送给自动化流程或网站。

使用 Make 的最简单方法

  1. 在 Make 中创建一个 Scenario。
  2. 第一个模块选择 Webhooks → Custom webhook。
  3. 创建 Webhook,复制 Make 提供的 HTTPS 地址。
  4. 点击 Run once,让 Make 开始等待 Surpasso 的测试请求。
  5. 在 Surpasso 选择 Webhook,填写连接名称并粘贴 Make 地址。
  6. 点击 创建并发送测试。
  7. 复制只显示一次的签名密钥,并安全保存。
  8. 在 Make 中添加负责创建或更新 CMS 文章的动作,并对应 article 中的字段。
  9. 开启 Scenario,再从 Surpasso 发布一篇测试文章。

第一次连接测试不包含文章内容。发送第一篇草稿或正式文章后,Make 才会看到完整字段。

Zapier 和 n8n

  • Zapier 使用 Webhooks by Zapier → Catch Hook。
  • n8n 使用 Webhook Trigger;工作流开启后复制 Production URL。
  • 在接收流程处于等待状态时,把 HTTPS 地址粘贴到 Surpasso。

Zapier 官方 Catch Hook 教程 展示了添加 Webhook Trigger、复制接收地址和发送测试数据的位置。

Zapier 官方教程中的 Catch Hook、Webhook 地址和测试数据操作说明

如果使用自建 Next.js 接收端,可下载的 Starter 已包含 app/api/surpasso-webhook/route.ts。它会基于未经修改的请求正文验证 HMAC 签名、拒绝 超过 5 分钟的时间戳,并返回接收端需要保存的 delivery 和幂等字段。持久化去重记录 仍应使用你自己的数据库或 Redis。

Surpasso 会发送什么

每次请求都是 JSON POST,event 说明发生的动作:

  • integration.test:设置或测试连接。
  • article.draft_created:创建网站草稿。
  • article.published:正式发布文章。
  • article.updated:更新网站上的已有文章。
{
  "schemaVersion": "1",
  "event": "article.published",
  "deliveryId": "unique-delivery-id",
  "idempotencyKey": "publication:unique-delivery-id",
  "occurredAt": "2026-10-13T10:00:00.000Z",
  "article": {
    "id": "article-id",
    "title": "文章标题",
    "slug": "article-title",
    "excerpt": "文章摘要",
    "contentHtml": "<p>完整 HTML 正文</p>",
    "contentMarkdown": "完整 Markdown 正文",
    "metaTitle": "SEO 标题",
    "metaDescription": "SEO 描述",
    "canonicalUrl": "https://example.com/blog/article-title",
    "author": "作者名称",
    "categories": ["分类"],
    "tags": ["标签"],
    "featuredImage": null,
    "images": []
  }
}

避免重复文章

临时故障可能触发重试。接收端应保存 deliveryId 或 idempotencyKey;再次收到相同 值时直接返回成功,不要创建第二篇文章。使用 article.id 找到并更新原来的 CMS 记录。

验证请求来源

请求包含 x-surpasso-event、x-surpasso-delivery、x-surpasso-timestamp 和 x-surpasso-signature。自建 Node.js 或 Next.js 接口可以验证原始正文:

import { createHmac, timingSafeEqual } from 'node:crypto';

export async function POST(request: Request) {
  const body = await request.text();
  const timestamp = request.headers.get('x-surpasso-timestamp') || '';
  const received = (request.headers.get('x-surpasso-signature') || '').replace(
    /^sha256=/,
    ''
  );
  const expected = createHmac('sha256', process.env.SURPASSO_WEBHOOK_SECRET!)
    .update(`${timestamp}.${body}`)
    .digest('hex');

  const timestampSeconds = Number(timestamp);
  const fresh =
    Number.isFinite(timestampSeconds) &&
    Math.abs(Date.now() / 1000 - timestampSeconds) <= 300;
  const valid =
    fresh &&
    /^[a-f0-9]{64}$/.test(received) &&
    timingSafeEqual(Buffer.from(received, 'hex'), Buffer.from(expected, 'hex'));
  if (!valid) return new Response('Unauthorized', { status: 401 });

  const event = JSON.parse(body);
  // 用数据库唯一约束或 Redis SET NX 原子保存 event.idempotencyKey。
  // 如果已经处理过,直接返回 204;否则按 event.article.id 创建或更新。
  return new Response(null, { status: 204 });
}

签名密钥只能放在服务端环境变量中。密钥泄露后,请断开并重新创建 Webhook。

接收端应拒绝超过 5 分钟的时间戳,并至少在整个重试周期内保存已经处理的 idempotencyKey。这两项检查可以阻止重放请求造成重复文章或旧内容覆盖。

接收地址要求

  • 使用公开 HTTPS 地址,不要把请求重定向到其他地址。
  • 尽快返回 2xx;耗时操作应先进入队列。
  • 不能要求先打开浏览器登录。
  • 能够接收 application/json 请求。

常见问题

**连接测试失败:**确认工作流已开启、填写的是 Production URL,并会返回 2xx。 Make 用户需要先点击 Run once。

**出现重复文章:**使用 idempotencyKey 去重,并用 article.id 更新原记录。

**签名无效:**必须使用未经解析的原始请求正文计算签名。