Docs

Webhook

最后更新: 2026-07-21阅读约 4 分钟

耗时较长的工具是异步完成的。与其反复轮询,不如注册一个 webhook:任务完成时,GREM 会以 POST 把事件推送到您的地址。

注册接收地址

在后台的 AI 工具 → API → Webhooks 中:添加一个公开的 https:// 地址,并勾选需要的事件。系统会一次性显示一个签名密钥whsec_…)— 请保存好,用于校验投递。

事件的命名形如 <来源>.<结果>。可用事件包括:

工具类:

  • valuation.completed, valuation.failed
  • content.completed, content.partial, content.failed
  • text.completed, text.failed
  • photo.completed, photo.failed
  • avatar.completed, avatar.failed
  • video.completed, video.failed
  • floorplan.completed, floorplan.failed
  • review.completed, review.failed
  • pdf.completed, pdf.failed

对象类:object.createdobject.updatedobject.publishedobject.deleted

线索类:lead.createdlead.updated。它们不含个人数据 — 只包含指向发生变化的线索的引用。

载荷

每次投递都是一个带 JSON 请求体的 POST

{
  "id": "…",
  "type": "content.completed",
  "createdAt": "2026-07-21T10:00:00.000Z",
  "data": { }
}

并带有这些请求头:

x-grem-event: content.completed
x-grem-signature: t=1721556000,v1=3b78a03b78058d229023db2a0b4ee072…

校验签名

**在信任任何一次投递之前,务必先校验签名。**签名是对字符串 "<t>.<原始请求体>" 计算的 HMAC-SHA256,密钥为您的 webhook 密钥。t 是 Unix 时间戳;请拒绝过旧的时间戳,以防重放攻击。

必须针对收到的原始请求体逐字节校验 — 在任何 JSON 解析之前。重新序列化 JSON 会改变字节内容,签名将无法匹配。

Node.js(Express)

const crypto = require('crypto');

function verifyGremSignature(secret, header, rawBody, toleranceSec = 300) {
  // header: "t=<unix>,v1=<hex>"
  const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // stale
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 || '');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Capture the RAW body for this route.
app.post('/grem-hook', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verifyGremSignature(process.env.GREM_WEBHOOK_SECRET, req.get('x-grem-signature'), raw)) {
    return res.status(400).send('invalid signature');
  }
  const event = JSON.parse(raw);
  // handle event.type / event.data …
  res.sendStatus(200);
});

Python(Flask)

import hmac, hashlib, time
from flask import Flask, request

def verify_grem_signature(secret, header, raw_body, tolerance=300):
    parts = dict(kv.split('=', 1) for kv in header.split(','))
    t = int(parts.get('t', 0))
    if not t or abs(time.time() - t) > tolerance:
        return False  # stale
    expected = hmac.new(secret.encode(), f"{t}.{raw_body}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get('v1', ''))

app = Flask(__name__)

@app.post('/grem-hook')
def grem_hook():
    raw = request.get_data(as_text=True)  # RAW body, before parsing
    if not verify_grem_signature(GREM_WEBHOOK_SECRET, request.headers.get('x-grem-signature', ''), raw):
        return 'invalid signature', 400
    event = request.get_json()
    # handle event['type'] / event['data'] …
    return '', 200

投递、重试与安全

  • 请尽快返回 2xx 状态。繁重的处理请异步完成。
  • 投递失败会以递增间隔重试;多次失败后该接收地址会被停用,并通知您。
  • 接收地址必须是公开的 https:// 网址。私有地址、回环地址和内网地址会被拒绝(防 SSRF)。
  • 签名密钥可随时在 Webhooks 标签页轮换;用发送测试可触发一次示例投递,并在投递日志中查看结果。

这篇文章对您有帮助吗?