/ 开发者指南
接收 Orova Social 的发布内容 通过 Webhook。
如何搭建一个接口来接收 Orova Social 发来的内容,并在处理之前先验证签名。
Orova Social 会直接发布到已连接的平台。如果内容需要送到别的地方——内部渠道、你自己的应用、Orova 还不支持的平台——就使用 API 渠道:Orova 把内容发到你的一个 HTTP 地址,并用密钥签名,之后的处理由你决定。本页面向开发者,完整说明请求约定。
/ 概览
工作方式
你在项目的 API 渠道里填写一个接收地址(https://)和一个密钥。Orova 会向该地址发送 POST 请求,请求体为 JSON,并带上 X-Orova-Signature 请求头——它是用你的密钥对请求体原始字节做的 HMAC-SHA256。你的接口验证签名、接收内容,然后返回 2xx 状态码。
- 接收地址必须是 https. 如果地址不是
https://或缺少主机名,Orova 会拒绝保存渠道,并给出代码api_url。 - 一个地址一个渠道. Orova 用接收地址本身来识别渠道。再次保存同一个地址是覆盖原渠道,不会生成重复项。
- 有“发送测试”按钮. 连接完成后,渠道上会有 Send test 按钮:Orova 立即向你的接口发送一个
orova.test数据包,并显示返回的 HTTP 状态码和往返毫秒数。
/ 配置
在 Orova 中配置
打开 Social → 项目 → 选择项目 → 渠道 标签页,找到 API 那一栏。它有两个输入框和一个按钮。
- 1
接收地址(webhook). 你的接口地址,以
https://开头。所有请求都会发到这里。 - 2
密钥. 用于签名的字符串。留空则由 Orova 随机生成——但界面不会再显示它,所以建议自己生成一串足够长的字符串粘贴进去,并在自己这边留存副本。
- 3
点击 Save channel. 渠道会出现在列表中并显示接收地址。密钥不会再次显示。
- 4
点击 Send test. Orova 会发送一个示例
orova.test数据包。接口返回 2xx,Orova 会提示 “Webhook 在 … ms 内返回 200”。返回其他状态码则连同代码一起报错;连不上则报网络错误。
/ 约定
请求约定
每次发送都是这样一个请求:
POST <your receiving URL>
Headers:
Content-Type: application/json
X-Orova-Signature: sha256=<hex HMAC-SHA256 of the raw body>
X-Orova-Event: orova.test
User-Agent: Orova-Social/1.0
Body (JSON):
{
"event": "orova.test",
"workspace_id": 12,
"project_id": 34,
"post": {
"title": "...",
"content": "...",
"media": [],
"channels": ["api"]
},
"sent_at": "2026-08-23T01:25:00Z"
}请求体是紧凑的 UTF-8 JSON,单行不换行。请读取 event 来区分事件类型——发送测试的数据包带 orova.test,X-Orova-Event 请求头也是同一个值。你的接口应当忽略未知字段而不是报错,这样以后新增字段不会影响你。
| 字段 | 类型 | 说明 |
|---|---|---|
| event | string | 事件类型。发送测试的数据包是 "orova.test"。 |
| workspace_id | integer | 发出该数据包的工作区 ID。 |
| project_id | integer | null | 渠道所属项目的 ID;渠道未归属项目时为 null。 |
| post | object | 内容本身。 |
| post.title | string | 内容标题。 |
| post.content | string | 正文文本。 |
| post.media | array | 该内容的附件;纯文字内容为空数组。 |
| post.channels | array of string | 这条内容面向的渠道类型,例如 ["api"]。 |
| sent_at | string | Orova 发送的时间,ISO 8601 UTC 格式,以 "Z" 结尾。 |
/ 验签
验证签名
X-Orova-Signature 请求头形如 sha256=<hex>,其中 <hex> 是用渠道密钥对请求体原始字节做的 HMAC-SHA256。请对收到的字节原样重新计算——先解析 JSON 再重新序列化是对不上的,哪怕只差一个空格签名就不同。
Node.js
// Node.js / Express — verify X-Orova-Signature, then answer fast
import express from "express";
import crypto from "node:crypto";
const app = express();
const OROVA_SECRET = process.env.OROVA_SECRET; // the key you saved in Orova
// Keep the RAW bytes: the signature covers them, not re-serialized JSON.
app.post(
"/orova/social",
express.raw({ type: "application/json", limit: "5mb" }),
(req, res) => {
const sent = req.get("x-orova-signature") || "";
const mine =
"sha256=" +
crypto.createHmac("sha256", OROVA_SECRET).update(req.body).digest("hex");
// Constant-time compare — never use ===.
const a = Buffer.from(sent);
const b = Buffer.from(mine);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).json({ error: "bad signature" });
}
const payload = JSON.parse(req.body.toString("utf8"));
if (payload.event === "orova.test") {
return res.status(200).json({ ok: true }); // the Send test button
}
// Real post: hand the work to a queue and reply straight away.
queuePost(payload).catch(console.error);
return res.status(200).json({ ok: true });
},
);
app.listen(3000);Python
# Python / Flask — same check, same rules
import hashlib
import hmac
import json
import os
from flask import Flask, request
app = Flask(__name__)
OROVA_SECRET = os.environ["OROVA_SECRET"].encode("utf-8")
@app.post("/orova/social")
def orova_social():
raw = request.get_data() # bytes, exactly as sent
mine = "sha256=" + hmac.new(OROVA_SECRET, raw, hashlib.sha256).hexdigest()
sent = request.headers.get("X-Orova-Signature", "")
if not hmac.compare_digest(mine, sent): # constant-time compare
return {"error": "bad signature"}, 401
payload = json.loads(raw)
if payload.get("event") == "orova.test":
return {"ok": True}, 200
queue_post(payload) # do the slow work later
return {"ok": True}, 200请使用恒定时间比较函数(crypto.timingSafeEqual、hmac.compare_digest),不要用普通的相等判断。签名对不上就返回 401 并停止——不要处理该数据包。
/ 回应
如何回应
Orova 只看 HTTP 状态码。2xx 都算成功;响应体内容随你。
- 尽快回应. 发送测试最多等待
10 秒。下载媒体、调用第三方 API 这类耗时工作请放入队列,先返回 2xx。 - Orova 不会自动重试. 这是刻意设计:对发布类接口自动重试,正是内容被发两次的原因。发送失败时,内容会被标记为
failed并附上原因,由用户在 Orova 里重新发布。 - 要能承受重复数据包. 用户重新发布时,同一个数据包会再来一次。请在你这边去重——例如记住已处理过的
workspace_id与sent_at组合。
/ 安全
安全
- 务必验证签名. 你的接口是公开地址,任何人都能调用。签名是唯一能证明数据包确实来自 Orova 的凭据。
- 密钥不要写进代码. 存放在环境变量或密钥管理服务中,不要硬编码进会提交的文件。
- 换密钥就重新保存一次. 填入同一个接收地址和新的密钥,点击 Save channel——Orova 会覆盖原渠道。别忘了同时更新你的接口。
- 断开渠道即删除密钥. 断开渠道后,Orova 会删除已保存的密钥,也不再向该地址发送内容。
/ 小结
约定就这些
一个 https 地址、一个密钥、一次验签、一个 2xx 状态码——Orova Social 的内容就能进入你的系统。之后要发到哪里、存到哪里、怎么记录,都由你决定。
