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>"
}