Webhook
1tokens은(는) 구독한 이벤트가 발생할 때마다 콘솔에 등록한 HTTPS URL로 서명된 POST 요청을 보냅니다. 콘솔의 Webhook 페이지에서 엔드포인트를 만들고 받을 이벤트를 선택하세요. 새 엔드포인트는 30초 이내에 적용됩니다.
이벤트 목록
모델
| 유형 | 이름 | data 필드 |
|---|---|---|
model.listed | 모델 등록 | model tenant_id previous_status status actor |
model.delisted | 모델 내림 | model tenant_id previous_status status actor |
model.deprecated | 모델 폐기 | model previous_status status actor |
model.price.changed | 판매가 변경 | model tenant_id pricing_status sale_price_mode multiplier price changes actor |
model.user_price.changed | 전용가 변경 | model user_id multiplier changes actor |
model.updated | 모델 업데이트 | model changes actor |
API Key
| 유형 | 이름 | data 필드 |
|---|---|---|
api_key.created | Key 생성 | api_key actor |
api_key.updated | Key 수정 | api_key changes actor |
api_key.revoked | Key 폐기 | api_key previous_status status actor |
api_key.expired | Key 만료 | api_key actor |
api_key.quota.exhausted | 한도 소진 | api_key quota_limit_usd consumed_usd actor |
api_key.rate_window.exceeded | 기간 한도 초과 | api_key window limit_usd consumed_usd actor |
계정
| 유형 | 이름 | data 필드 |
|---|---|---|
account.balance.low | 잔액 부족 | account threshold_usd request_id actor |
account.balance.exhausted | 잔액 소진 | account reason request_id actor |
account.credit.topup | 충전 | account amount_usd amount_credits source order_no actor |
account.credit.adjusted | 잔액 조정 | account delta_usd delta_credits kind reason actor |
account.trial.granted | 체험 지급 | account amount_credits actor |
account.disabled | 계정 비활성 | account previous_status status actor |
account.enabled | 계정 복구 | account previous_status status actor |
account.password.reset | 비밀번호 재설정 | account actor |
주문
| 유형 | 이름 | data 필드 |
|---|---|---|
order.created | 주문 생성 | order actor |
order.paid | 결제 완료 | order paid_at previous_status status actor |
order.failed | 결제 실패 | order reason previous_status status actor |
order.redeemed | 코드 사용 | order code_masked actor |
구독
| 유형 | 이름 | data 필드 |
|---|---|---|
subscription.started | 구독 시작 | subscription previous_status status actor |
subscription.renewed | 구독 갱신 | subscription actor |
subscription.upgraded | 구독 업그레이드 | subscription changes actor |
subscription.cancelled | 구독 취소 | subscription previous_status status actor |
subscription.payment_failed | 갱신 실패 | subscription reason actor |
subscription.quota.exhausted | 할당량 소진 | subscription request_id actor |
작업
| 유형 | 이름 | data 필드 |
|---|---|---|
task.video.succeeded | 영상 완료 | task video_count actor |
task.video.failed | 영상 실패 | task actor |
task.generation.succeeded | 생성 완료 | task media output_count actor |
task.generation.failed | 생성 실패 | task media actor |
공지
| 유형 | 이름 | data 필드 |
|---|---|---|
content.changelog.published | 변경 로그 게시 | entry actor |
content.article.published | 게시글 게시 | article tenant_id previous_status status actor |
content.article.unpublished | 게시글 내림 | article tenant_id previous_status status actor |
Webhook
| 유형 | 이름 | data 필드 |
|---|---|---|
webhook.endpoint.auto_disabled | 엔드포인트 자동 비활성 | endpoint reason consecutive_failures actor |
페이로드 형식
모든 이벤트는 같은 봉투를 사용합니다: id(이벤트 고유 id), type, domain, created_at(ISO 8601 UTC), api_version, data. data의 키는 snake_case, 시각은 _at로 끝나며, 금액은 십진 문자열(_usd / _credits), 값이 없는 필드는 명시적으로 null입니다. 재시도로 같은 이벤트가 여러 번 전달될 수 있으니 id로 중복을 제거하세요.
{ "id": "evt_01JBXK6Q3N7Y8R2V4W5X6Z7A8B", "type": "model.price.changed", "domain": "model", "created_at": "2026-11-04T08:12:33Z", "api_version": "2026-11-04", "data": { }}요청 헤더
POST https://your-server.example/webhooksContent-Type: application/jsonUser-Agent: Webhooks/1.0X-Event-Id: evt_01JBXK6Q3N7Y8R2V4W5X6Z7A8BX-Event-Type: model.price.changedX-Delivery-Id: 123456X-Timestamp: 1762243953X-Signature: v1=5f1c…서명 검증
엔드포인트 생성 시 표시된 시크릿으로 X-Timestamp 값 + . + 원본 요청 본문 문자열에 대해 HMAC-SHA256을 계산하고, X-Signature의 v1= 뒤 16진수와 상수 시간 비교하세요. 타임스탬프가 현재 시각과 5분 이상 차이 나면 거부하세요. 먼저 2xx를 응답한 뒤 처리하고, 전체 응답은 10초 이내에 끝내야 합니다.
import { createHmac, timingSafeEqual } from "node:crypto";export function verify(secret, headers, rawBody) { const ts = headers["x-timestamp"]; if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; const expected = createHmac("sha256", secret).update(ts + "." + rawBody).digest("hex"); const given = String(headers["x-signature"]).replace(/^v1=/, ""); return given.length === expected.length && timingSafeEqual(Buffer.from(given, "hex"), Buffer.from(expected, "hex"));}import hmac, hashlib, timedef verify(secret: str, headers: dict, raw_body: bytes) -> bool: ts = headers["X-Timestamp"] if abs(time.time() - int(ts)) > 300: return False expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest() given = headers["X-Signature"].removeprefix("v1=") return hmac.compare_digest(given, expected)func verify(secret string, ts, sig string, body []byte) bool { t, _ := strconv.ParseInt(ts, 10, 64) if math.Abs(float64(time.Now().Unix()-t)) > 300 { return false } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(ts + ".")) mac.Write(body) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(strings.TrimPrefix(sig, "v1=")), []byte(expected))}재시도 및 자동 비활성화
2xx가 아닌 응답이나 10초 내 무응답은 실패로 간주합니다. 각 전달은 아래 표의 간격으로 최대 7회 시도하며, 모두 실패하면 소진으로 표시됩니다. 같은 엔드포인트에서 연속 10건이 소진되면 자동으로 비활성화되고 이메일로 알립니다. 수정 후 콘솔에서 다시 활성화하세요. 모든 전달은 콘솔에서 수동으로 재전송할 수 있습니다.
| 시도 횟수 | 이전 시도 후 대기 |
|---|---|
| 1 | 0 |
| 2 | 1 min |
| 3 | 5 min |
| 4 | 30 min |
| 5 | 2 h |
| 6 | 6 h |
| 7 | 24 h |
엔드포인트 요구 사항
URL은 공개적으로 접근 가능한 HTTPS 주소여야 하며, 사설·루프백·링크 로컬 대역으로 해석되는 호스트 이름은 거부됩니다. 리디렉션을 따르지 않으며 응답 본문은 최대 64 KB까지만 읽습니다.

