Webhooks
創建 Webhook
註冊一個新的 Webhook 端點以接收實時事件通知。
POST
/
api
/
v1
/
webhooks
創建 Webhook
curl --request POST \
--url https://api.example.com/api/v1/webhooks \
--header 'Content-Type: application/json' \
--data '
{
"url": "<string>",
"event_types": [
"<string>"
],
"secret": "<string>",
"description": "<string>"
}
'import requests
url = "https://api.example.com/api/v1/webhooks"
payload = {
"url": "<string>",
"event_types": ["<string>"],
"secret": "<string>",
"description": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
url: '<string>',
event_types: ['<string>'],
secret: '<string>',
description: '<string>'
})
};
fetch('https://api.example.com/api/v1/webhooks', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/webhooks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'url' => '<string>',
'event_types' => [
'<string>'
],
'secret' => '<string>',
'description' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/webhooks"
payload := strings.NewReader("{\n \"url\": \"<string>\",\n \"event_types\": [\n \"<string>\"\n ],\n \"secret\": \"<string>\",\n \"description\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/webhooks")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"<string>\",\n \"event_types\": [\n \"<string>\"\n ],\n \"secret\": \"<string>\",\n \"description\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/webhooks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"url\": \"<string>\",\n \"event_types\": [\n \"<string>\"\n ],\n \"secret\": \"<string>\",\n \"description\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"url": "<string>",
"event_types": [
"<string>"
],
"enabled": true,
"signing_secret": "<string>",
"created_at": "<string>"
}請求
string
必填
用於接收事件推送的 HTTPS URL。生產環境中必須使用
https://。該地址會經過 SSRF 校驗——私有 IP、雲元數據端點以及非 HTTP 協議均會被攔截。string[]
必填
要訂閱的事件類型數組。使用
"*" 訂閱所有事件,或指定具體類型,如 "completion.created"。有效類型:completion.created、job.completed、job.failed、workflow.completed、workflow.failed、skill.installed、skill.uninstalled、skill.submitted、skill.approved、skill.rejected、skill.suspended、connector.created、connector.status_changed、credits.low、key.created、key.revoked、*string
可選的自定義簽名密鑰(16–256 個字符)。若省略,將生成一個密碼學隨機的 32 字節十六進制密鑰。
string
Webhook 的可選描述(最多 256 個字符)。
響應
string
該 Webhook 的唯一 UUID。
string
已註冊的端點 URL。
string[]
已訂閱的事件類型。
boolean
創建時始終為
true。string
HMAC-SHA256 簽名密鑰。僅顯示一次——請立即保存。 用於在你的服務器上驗證 Webhook 簽名。
string
ISO 8601 時間戳。
身份驗證
需要具有completions 權限範圍的組織級 API 密鑰。
Authorization: Bearer theo_sk_...
示例
curl -X POST https://www.hitheo.ai/api/v1/webhooks \
-H "Authorization: Bearer $THEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/theo",
"event_types": ["completion.created", "job.completed"]
}'
import Theo from "@hitheo/sdk";
const theo = new Theo({ apiKey: process.env.THEO_API_KEY });
const webhook = await theo.createWebhook({
url: "https://your-server.com/webhooks/theo",
event_types: ["completion.created", "job.completed"],
});
// Save webhook.signing_secret — shown once only
console.log("Signing secret:", webhook.signing_secret);
響應
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"url": "https://your-server.com/webhooks/theo",
"event_types": ["completion.created", "job.completed"],
"enabled": true,
"signing_secret": "8f3a1b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1",
"created_at": "2026-04-15T12:00:00.000Z",
"_note": "Save the signing_secret — it will not be shown again."
}
限制
- 每個組織可創建的活躍 Webhook 數量有上限。如需更高配額,請聯繫支持團隊。
- URL 必須通過 SSRF 校驗(不允許私有 IP,不允許雲元數據端點)。
- 生產環境中必須使用 HTTPS。
簽名驗證
每次推送都會包含三個用於驗證的請求頭:Theo-Webhook-Signature: v1=<hex>
Theo-Webhook-Timestamp: <unix_seconds>
Theo-Webhook-Id: <event_id>
HMAC-SHA256(signing_secret, timestamp + "." + raw_body) 並與簽名進行比對。若時間戳與你本地時鐘相差超過 5 分鐘,應拒絕該請求。
const crypto = require("crypto");
function verifyWebhook(rawBody, headers, secret) {
const timestamp = headers["theo-webhook-timestamp"];
const signature = headers["theo-webhook-signature"];
// Replay protection
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (age > 300) throw new Error("Timestamp too old");
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const [, hash] = signature.split("=");
if (hash !== expected) throw new Error("Invalid signature");
return JSON.parse(rawBody);
}
這個頁面有幫助嗎?
創建 Webhook
curl --request POST \
--url https://api.example.com/api/v1/webhooks \
--header 'Content-Type: application/json' \
--data '
{
"url": "<string>",
"event_types": [
"<string>"
],
"secret": "<string>",
"description": "<string>"
}
'import requests
url = "https://api.example.com/api/v1/webhooks"
payload = {
"url": "<string>",
"event_types": ["<string>"],
"secret": "<string>",
"description": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
url: '<string>',
event_types: ['<string>'],
secret: '<string>',
description: '<string>'
})
};
fetch('https://api.example.com/api/v1/webhooks', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/webhooks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'url' => '<string>',
'event_types' => [
'<string>'
],
'secret' => '<string>',
'description' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/webhooks"
payload := strings.NewReader("{\n \"url\": \"<string>\",\n \"event_types\": [\n \"<string>\"\n ],\n \"secret\": \"<string>\",\n \"description\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/webhooks")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"<string>\",\n \"event_types\": [\n \"<string>\"\n ],\n \"secret\": \"<string>\",\n \"description\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/webhooks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"url\": \"<string>\",\n \"event_types\": [\n \"<string>\"\n ],\n \"secret\": \"<string>\",\n \"description\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"url": "<string>",
"event_types": [
"<string>"
],
"enabled": true,
"signing_secret": "<string>",
"created_at": "<string>"
}