开发者文档
快速开始
只需四步,即可创建 API 密钥、发出第一个请求并安全地接收 Webhook。
1. 创建 API 密钥
在账户控制台创建集成并签发 API 密钥。完整密钥只显示一次,请安全地存入密钥管理服务。
- 打开 账户 → API & Webhooks,为个人工作区或你拥有的团队空间创建一个集成。
- 选择所需的权限范围:
notes:read、transcripts:read、summaries:read、webhooks:manage。 - 密钥形如
alt_live_{key_id}.{secret},且只显示一次。请存入密钥管理服务。
把 API 密钥添加到 shell 环境变量后,下面的命令即可原样运行:
shell
export ALT_API_KEY="alt_live_...paste-your-key-here..."2. 获取已有笔记
把 API 返回的 cursor 传入下一次请求,即可继续获取完整的笔记列表。随后再逐条获取每篇笔记的转录和摘要。
curl
curl 'https://public-api.altalt.io/v1/notes?limit=100' \
-H "Authorization: Bearer $ALT_API_KEY"
# Follow next_cursor until has_more is false
curl 'https://public-api.altalt.io/v1/notes?limit=100&cursor=NEXT_CURSOR' \
-H "Authorization: Bearer $ALT_API_KEY"
# Fetch content per note (scopes: transcripts:read / summaries:read)
curl 'https://public-api.altalt.io/v1/notes/NOTE_ID/transcript' \
-H "Authorization: Bearer $ALT_API_KEY"
curl 'https://public-api.altalt.io/v1/notes/NOTE_ID/summary' \
-H "Authorization: Bearer $ALT_API_KEY"首次同步完成后,无需再次获取全部笔记。使用 ?updated_after=<last sync time> 只请求上次同步后发生变更的笔记,或改用 Webhook。
3. 注册 webhook 端点
注册一个公网 HTTPS 端点来接收新增和变更通知,无需反复轮询 API。
curl
curl -X POST 'https://public-api.altalt.io/v1/webhook-endpoints' \
-H "Authorization: Bearer $ALT_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com/webhooks/alt",
"events": ["note.ended", "note.summary.generated", "note.updated", "note.deleted"]
}'响应中包含用于验证 Webhook 签名的 signing_secret(whsec_...),完整值只显示一次。端点初始状态为 pending_verification;接收端对 verification 事件返回 2xx 后,端点即会启用。你也可以在控制台中无需编写代码完成注册。
4. 验证 webhook 签名
对每个 Webhook 请求验证 Standard Webhooks 签名,以确认请求来自 Alt。使用 event_id 跳过重复事件,先返回响应,再通过 REST API 获取最新内容。
Node.js
Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
import http from "node:http";
// whsec_... secret from endpoint creation (shown once). Keep it server-side.
const SECRET = process.env.ALT_WEBHOOK_SECRET;
const secretBytes = Buffer.from(SECRET.slice("whsec_".length), "base64url");
const TOLERANCE_SECONDS = 300;
function isValidSignature(headers, rawBody) {
const id = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
const signatureHeader = headers["webhook-signature"];
if (!id || !timestamp || !signatureHeader) return false;
// Reject stale timestamps (replay protection)
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false;
const expected = createHmac("sha256", secretBytes)
.update(`${id}.${timestamp}.${rawBody}`)
.digest("base64");
// Header may contain multiple space-delimited signatures: "v1,abc v1,def"
return String(signatureHeader)
.split(" ")
.some((part) => {
const [version, signature] = part.split(",");
if (version !== "v1" || !signature) return false;
const a = Buffer.from(signature);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
});
}
http
.createServer((req, res) => {
if (req.method !== "POST" || req.url !== "/webhooks/alt") {
res.writeHead(404).end();
return;
}
let rawBody = "";
req.on("data", (chunk) => (rawBody += chunk));
req.on("end", () => {
if (!isValidSignature(req.headers, rawBody)) {
res.writeHead(401).end();
return;
}
const event = JSON.parse(rawBody);
// 1. Dedupe on event.event_id (deliveries are at-least-once).
// 2. Enqueue for async processing, then ack fast.
// 3. Fetch the note from the REST API; apply only if revision is newer.
console.log(event.event_type, event.data.note_id, event.data.revision);
res.writeHead(204).end();
});
})
.listen(3000);Python
Python
import base64, hashlib, hmac, json, os, time
from http.server import BaseHTTPRequestHandler, HTTPServer
# whsec_... secret from endpoint creation (shown once). Keep it server-side.
raw_secret = os.environ["ALT_WEBHOOK_SECRET"].removeprefix("whsec_")
SECRET = base64.urlsafe_b64decode(raw_secret + "=" * (-len(raw_secret) % 4))
TOLERANCE_SECONDS = 300
def is_valid_signature(headers, raw_body: bytes) -> bool:
msg_id = headers.get("webhook-id", "")
timestamp = headers.get("webhook-timestamp", "")
signature_header = headers.get("webhook-signature", "")
if not msg_id or not timestamp or not signature_header:
return False
# Reject stale timestamps (replay protection)
if abs(time.time() - float(timestamp)) > TOLERANCE_SECONDS:
return False
signed_content = f"{msg_id}.{timestamp}.".encode() + raw_body
digest = hmac.new(SECRET, signed_content, hashlib.sha256).digest()
expected = base64.b64encode(digest).decode()
# Header may contain multiple space-delimited signatures: "v1,abc v1,def"
for part in signature_header.split(" "):
version, _, signature = part.partition(",")
if version == "v1" and signature and hmac.compare_digest(signature, expected):
return True
return False
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != "/webhooks/alt":
self.send_response(404); self.end_headers(); return
raw_body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
if not is_valid_signature(self.headers, raw_body):
self.send_response(401); self.end_headers(); return
event = json.loads(raw_body)
# 1. Dedupe on event["event_id"] (deliveries are at-least-once).
# 2. Enqueue for async processing, then ack fast.
# 3. Fetch the note from the REST API; apply only if revision is newer.
print(event["event_type"], event["data"]["note_id"], event["data"]["revision"])
self.send_response(204); self.end_headers()
HTTPServer(("", 3000), Handler).serve_forever()签名遵循 Standard Webhooks 规范,因此可以使用官方 standardwebhooks 库(npm / PyPI)。关于重复事件、到达顺序和遗漏变更的处理方法,请参见 Webhooks。