통합 가이드인증엔드포인트 규약모델오류Webhook
1tokens1tokens
시작하기API 참조

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.createdKey 생성api_key actor
api_key.updatedKey 수정api_key changes actor
api_key.revokedKey 폐기api_key previous_status status actor
api_key.expiredKey 만료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건이 소진되면 자동으로 비활성화되고 이메일로 알립니다. 수정 후 콘솔에서 다시 활성화하세요. 모든 전달은 콘솔에서 수동으로 재전송할 수 있습니다.

시도 횟수이전 시도 후 대기
10
21 min
35 min
430 min
52 h
66 h
724 h

엔드포인트 요구 사항

URL은 공개적으로 접근 가능한 HTTPS 주소여야 하며, 사설·루프백·링크 로컬 대역으로 해석되는 호스트 이름은 거부됩니다. 리디렉션을 따르지 않으며 응답 본문은 최대 64 KB까지만 읽습니다.

오류

Previous Page

On this page

이벤트 목록모델API Key계정주문구독작업공지Webhook페이로드 형식요청 헤더서명 검증재시도 및 자동 비활성화엔드포인트 요구 사항
{
  "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/webhooks
Content-Type: application/json
User-Agent: Webhooks/1.0
X-Event-Id: evt_01JBXK6Q3N7Y8R2V4W5X6Z7A8B
X-Event-Type: model.price.changed
X-Delivery-Id: 123456
X-Timestamp: 1762243953
X-Signature: v1=5f1c…
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, time

def 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))
}