🔗 Webhook 发布集成文档
指导你的服务器如何接收 GeoSight 的内容发布请求
工作原理
- 你在 GeoSight 设置页配置 Webhook URL(你的服务器端点)
- 用户在内容详情页点击「Webhook 发布」
- GeoSight 将内容以 JSON 格式 POST 到你的 URL
- 你的服务器接收内容,发布到自己的网站/CMS
- 你的服务器返回发布结果(含发布后的 URL)
- GeoSight 自动记录发布 URL,后续可追踪引用归因和收录状态
请求规范
GeoSight 发送到你服务器的 HTTP 请求
HTTP 方法
POSTContent-Type
application/json请求头
| Header | 说明 |
|---|---|
| Content-Type | application/json |
| X-GeoSight-Signature | HMAC-SHA256 签名(格式:sha256=xxxx) |
| X-GeoSight-Event | 事件类型:content.publish 或 webhook.test |
| X-GeoSight-Delivery | 投递唯一 ID(UUID),用于幂等检查 |
| X-GeoSight-Timestamp | Unix 时间戳(秒),用于签名和重放防护 |
请求体(Payload)
JSON 格式的内容数据
{
"event": "content.publish",
"timestamp": "2026-08-29T22:00:00+08:00",
"brand": {
"name": "你的品牌名",
"industry": "行业名称"
},
"content": {
"title": "内容标题",
"body_md": "# Markdown 格式内容\n\n## 小标题\n\n正文...",
"body_html": "<h1>HTML 格式内容</h1><h2>小标题</h2><p>正文...</p>",
"body_plain": "纯文本格式内容(无 Markdown/HTML 标记)",
"json_ld": {
"@context": "https://schema.org",
"@graph": [
{ "@type": "Article", "headline": "标题", ... },
{ "@type": "FAQPage", "mainEntity": [...] }
]
},
"prompt_text": "触发此内容的用户提示词",
"journey_stage": "research",
"strategy_mode": "conservative",
"strategy_score": 78
},
"seo": {
"suggested_slug": "建议的 URL slug",
"meta_title": "建议的 SEO 标题",
"meta_description": "建议的 Meta 描述(≤160字)"
},
"source": {
"gap_id": "GeoSight 内容差距 ID",
"platform": "geosight"
}
}字段说明:
body_md— Markdown 原文,适合存储和后续编辑body_html— 简易 HTML 转换,适合直接发布到 CMSbody_plain— 纯文本,适合搜索索引json_ld— 结构化数据,建议嵌入页面<head>的<script type="application/ld+json">journey_stage— 用户旅程阶段:discovery(发现)/research(调研)/decision(决策)strategy_score— GEO 策略得分(0-100),基于论文 S1-S6 元素量化
签名验证(必须)
验证请求确实来自 GeoSight,防止伪造
⚠ 安全要求
你的服务器必须验证签名。未经验证的请求可能被恶意第三方伪造。
签名算法:
- 拼接字符串:
timestamp + "." + request_body - 使用你的 签名密钥(在 GeoSight 设置页配置)做 HMAC-SHA256
- 与请求头
X-GeoSight-Signature对比
received_signature = request.headers["X-GeoSight-Signature"]
timestamp = request.headers["X-GeoSight-Timestamp"]
body = request.raw_body // 原始 JSON 字符串
expected = "sha256=" + HMAC-SHA256(secret, timestamp + "." + body)
if received_signature != expected:
return 401 "签名无效"
// 可选:检查 timestamp 在 5 分钟内,防止重放
if abs(now() - int(timestamp)) > 300:
return 401 "请求已过期"响应规范
你的服务器需要返回的 JSON 格式
{
"ok": true,
"url": "https://your-site.com/blog/your-article-slug",
"message": "发布成功"
}{
"ok": true,
"url": null,
"message": "已接收,稍后发布"
}{
"ok": false,
"error": "分类不存在"
}• 返回 ok: true 且有 url 时,GeoSight 会自动记录发布 URL 并可用于收录推送和引用归因
• 返回 ok: true 但无 url 时,GeoSight 标记为已接收但不记录 URL
• 返回 ok: false 或 HTTP 非 2xx 时,GeoSight 显示错误信息
幂等处理(建议)
GeoSight 每次投递都会生成唯一的 X-GeoSight-Delivery(UUID)。建议你的服务器记录已处理的 Delivery ID:
- 收到请求时,先检查 Delivery ID 是否已处理过
- 如果已处理,直接返回上次的结果(不重复发布)
- 防止网络重试导致重复发布
示例代码
const express = require('express');
const crypto = require('crypto');
const app = express();
const SECRET = '你在GeoSight设置页看到的签名密钥';
const processedDeliveries = new Set(); // 生产环境用 Redis/DB
app.post('/api/geosight-webhook', express.raw({ type: '*/*' }), (req, res) => {
// 1. 验签
const sig = req.headers['x-geosight-signature'];
const timestamp = req.headers['x-geosight-timestamp'];
const body = req.body.toString();
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET)
.update(timestamp + '.' + body).digest('hex');
if (sig !== expected) {
return res.status(401).json({ ok: false, error: '签名无效' });
}
// 2. 幂等检查
const deliveryId = req.headers['x-geosight-delivery'];
if (processedDeliveries.has(deliveryId)) {
return res.json({ ok: true, url: null, message: '已处理' });
}
// 3. 解析内容
const data = JSON.parse(body);
const { content, seo, brand } = data;
// 4. 发布到你的 CMS/数据库
const slug = seo.suggested_slug;
const url = `https://your-site.com/blog/${slug}`;
// 示例:写入数据库
// await db.posts.create({
// title: content.title,
// body: content.body_html,
// slug: slug,
// jsonLd: content.json_ld,
// metaTitle: seo.meta_title,
// metaDescription: seo.meta_description,
// });
console.log(`[GeoSight] 收到内容: ${content.title}`);
// 5. 记录并返回
processedDeliveries.add(deliveryId);
res.json({ ok: true, url, message: '发布成功' });
});
app.listen(3000);import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
app = FastAPI()
SECRET = "你在GeoSight设置页看到的签名密钥"
processed = set()
@app.post("/api/geosight-webhook")
async def geosight_webhook(request: Request):
# 1. 验签
body = await request.body()
sig = request.headers.get("x-geosight-signature", "")
timestamp = request.headers.get("x-geosight-timestamp", "")
expected = "sha256=" + hmac.new(
SECRET.encode(),
f"{timestamp}.{body.decode()}".encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(sig, expected):
raise HTTPException(401, "签名无效")
# 2. 幂等检查
delivery_id = request.headers.get("x-geosight-delivery", "")
if delivery_id in processed:
return {"ok": True, "url": None, "message": "已处理"}
# 3. 解析内容
import json
data = json.loads(body)
content = data["content"]
seo = data["seo"]
# 4. 发布到你的 CMS
slug = seo["suggested_slug"]
url = f"https://your-site.com/blog/{slug}"
# await db.posts.create(title=content["title"], body=content["body_html"], ...)
# 5. 记录并返回
processed.add(delivery_id)
return {"ok": True, "url": url, "message": "发布成功"}测试方法
- 在 GeoSight 设置页填写你的 Webhook URL 和签名密钥,点击「保存配置」
- 点击「测试连接」—— GeoSight 会发送一个
webhook.test事件到你的服务器 - 你的服务器正常响应即可(验签通过 + 返回 JSON)
- 进入任意内容详情页,点击「🔗 Webhook 发布」进行实际内容投递测试
调试提示:
测试连接时,X-GeoSight-Event 为 webhook.test,Payload 只有 event 和 timestamp 字段。你的代码应根据 event 字段区分测试事件和正式发布事件。
常见问题
Q: 我没有服务器怎么办?
A: 可以使用 Zapier / Make / n8n 等自动化平台的 Webhook 触发器接收数据,再转发到 WordPress / Notion / 其他 CMS。
Q: 支持批量发布吗?
A: 当前每次点击「Webhook 发布」发送一条内容。批量发布可多次点击,每次独立投递。
Q: 发布后内容更新了怎么办?
A: 再次点击「Webhook 发布」会重新投递最新版本。你的服务器可通过 source.gap_id 判断是新增还是更新。
Q: 签名密钥泄露了怎么办?
A: 在设置页重新保存 Webhook 配置(留空密钥字段会自动生成新密钥),同时更新你服务器的密钥。