개발자 문서

퀵스타트

API 키를 발급하고 첫 요청을 보낸 뒤 웹훅을 안전하게 수신하기까지, 4단계로 시작할 수 있습니다.

1. API 키 만들기

계정 콘솔에서 API 연동을 생성하고 키를 발급하세요. 전체 키는 한 번만 표시되므로 시크릿 매니저에 안전하게 보관해야 합니다.

  • 계정 → API & Webhooks에서 개인 워크스페이스 또는 본인이 소유한 팀스페이스에 사용할 연동을 만드세요.
  • 연동에 필요한 권한 범위(scope)를 선택하세요: notes:read, transcripts:read, summaries:read, webhooks:manage.
  • API 키는 alt_live_{key_id}.{secret} 형태이며 전체 값은 한 번만 표시됩니다. 바로 시크릿 매니저에 보관하세요.

아래 명령을 실행할 수 있도록 API 키를 셸 환경 변수로 등록하세요:

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>로 마지막 동기화 이후 변경된 노트만 조회하거나 웹훅을 사용하세요.

3. 웹훅 엔드포인트 등록하기

노트가 생성되거나 변경되었는지 반복해서 조회할 필요가 없도록, 알림을 받을 공개 HTTPS 엔드포인트를 등록하세요.

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

응답에는 웹훅 서명을 검증할 때 사용하는 signing_secret(whsec_...)이 포함되며, 전체 값은 한 번만 표시됩니다. 엔드포인트는 pending_verification 상태로 시작합니다. 수신 서버가 endpoint.verification 이벤트에 2xx로 응답하면 활성화됩니다. 코드 없이 콘솔에서 등록할 수도 있습니다.

4. 웹훅 서명 검증하기

모든 웹훅 요청의 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)를 사용할 수 있습니다. 중복 이벤트, 도착 순서, 누락된 변경 사항을 처리하는 방법은 웹훅 문서를 참고하세요.