OROVA.VN — BIZ AI AGENT

/ 开发者指南

接收 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.testX-Orova-Event 请求头也是同一个值。你的接口应当忽略未知字段而不是报错,这样以后新增字段不会影响你。

字段类型说明
eventstring事件类型。发送测试的数据包是 "orova.test"。
workspace_idinteger发出该数据包的工作区 ID。
project_idinteger | null渠道所属项目的 ID;渠道未归属项目时为 null。
postobject内容本身。
post.titlestring内容标题。
post.contentstring正文文本。
post.mediaarray该内容的附件;纯文字内容为空数组。
post.channelsarray of string这条内容面向的渠道类型,例如 ["api"]。
sent_atstringOrova 发送的时间,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.timingSafeEqualhmac.compare_digest),不要用普通的相等判断。签名对不上就返回 401 并停止——不要处理该数据包。

/ 回应

如何回应

Orova 只看 HTTP 状态码。2xx 都算成功;响应体内容随你。

  • 尽快回应. 发送测试最多等待 10 秒。下载媒体、调用第三方 API 这类耗时工作请放入队列,先返回 2xx。
  • Orova 不会自动重试. 这是刻意设计:对发布类接口自动重试,正是内容被发两次的原因。发送失败时,内容会被标记为 failed 并附上原因,由用户在 Orova 里重新发布。
  • 要能承受重复数据包. 用户重新发布时,同一个数据包会再来一次。请在你这边去重——例如记住已处理过的 workspace_idsent_at 组合。

/ 安全

安全

  • 务必验证签名. 你的接口是公开地址,任何人都能调用。签名是唯一能证明数据包确实来自 Orova 的凭据。
  • 密钥不要写进代码. 存放在环境变量或密钥管理服务中,不要硬编码进会提交的文件。
  • 换密钥就重新保存一次. 填入同一个接收地址和新的密钥,点击 Save channel——Orova 会覆盖原渠道。别忘了同时更新你的接口。
  • 断开渠道即删除密钥. 断开渠道后,Orova 会删除已保存的密钥,也不再向该地址发送内容。

/ 小结

约定就这些

一个 https 地址、一个密钥、一次验签、一个 2xx 状态码——Orova Social 的内容就能进入你的系统。之后要发到哪里、存到哪里、怎么记录,都由你决定。

← 返回指南库

/ 需要帮助?

接口一直接不上?打开工作区里的支持板块,或者 给我们发消息。