/ 開發者說明
接收 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 的內容就能進入你的系統。之後要發到哪裡、存到哪裡、怎麼記錄,都由你決定。
