에이전트 이벤트를 위한 웹훅 설정
에이전트 작업이 완료되거나 실패할 때, 에이전트가 승인을 대기할 때, 또는 문서 사이트가 게시될 때 사용자의 https 엔드포인트에서 서명된 POST를 받으세요. 이벤트, 페이로드, 서명 검증, 재시도 및 자동 비활성화 기능을 제공합니다.
웹훅은 에이전트 작업 완료나 승인 대기 등 특정 상황이 발생할 때 즉시 사용자의 엔드포인트로 서명된 POST을 전송하므로, 시스템에서 폴링(poll)할 필요가 없습니다. 이 문서에서는 엔드포인트 추가, 이벤트, 페이로드, 서명 검증 방법 및 전송 실패 시 처리되는 방식에 대해 다룹니다.
시작하기 전에
- 웹훅은 Pro, Business 및 Custom 플랜에서 사용할 수 있습니다("웹훅은 Pro 플랜 이상에서 제공됩니다.").
- 워크스페이스 소유자와 관리자만 웹훅을 관리할 수 있습니다. 다른 멤버에게는 "소유자와 관리자만 웹훅을 관리할 수 있습니다"라는 메시지가 표시됩니다.
- 10초 이내에
2xx상태 코드로 응답하는 공개 https 주소가 필요합니다. - 워크스페이스당 최대 10개의 웹훅을 가질 수 있습니다.
웹훅 추가
웹훅 탭 열기
워크스페이스 메뉴를 열고 AI agents & API를 선택한 다음 Webhooks를 엽니다. 새 웹훅을 클릭하세요.

각 웹훅은 이벤트, 마지막 전송 시간, 시크릿의 끝부분을 보여줍니다. 엔드포인트 입력
- Endpoint URL: "공개 HTTPS 주소입니다." 예:
https://example.com/clevera/webhooks. - 설명: 선택 사항이며 최대 200자까지 입력 가능합니다. 예: "Slack 릴레이".
- Endpoint URL: "공개 HTTPS 주소입니다." 예:
이벤트 선택
수신할 이벤트를 체크하거나 새 이벤트를 포함한 모든 이벤트를 선택하세요. 곧 출시로 표시된 이벤트는 수락되지만 "아직 이 이벤트를 전송하는 항목이 없습니다."

곧 출시로 표시된 이벤트는 수락되지만 아직 전송되지 않습니다. 생성 및 시크릿 저장
웹훅 생성을 클릭하세요. 서명 시크릿(
whsec_으로 시작)이 한 번 표시됩니다: "지금 복사하세요. 다시는 볼 수 없습니다. Clevera-Signature 헤더를 확인하는 데 사용하세요." 비밀번호처럼 안전하게 저장한 다음 저장했습니다를 클릭하세요.테스트 이벤트 보내기
웹훅의 ⋯ 메뉴를 열고 테스트 이벤트 보내기를 선택하세요. Clevera가 서명된
ping이벤트를 한 번 전송합니다(재시도 없음). "테스트 이벤트 전송됨"은 Clevera에서 이벤트가 발송되었음을 의미합니다. 엔드포인트의 응답은 전송에서 확인하세요.
이벤트
| 이벤트 | 전송 시점 |
|---|---|
job.started |
에이전트가 시작한 작업이 실행되기 시작할 때. |
job.needs_input |
작업이 승인 등 사람의 처리를 기다릴 때. 링크와 요청 만료 시간이 포함됩니다. |
job.succeeded |
작업이 완료되었을 때. |
job.failed |
작업이 실패했을 때. 오류 코드가 포함됩니다. |
job.canceled |
작업이 취소되었거나, 승인이 거부되거나 만료되었을 때. |
approval.requested |
에이전트가 에이전트 요청에서 승인을 요청했을 때. |
approval.decided |
누군가 요청을 승인하거나 거부했거나, 요청이 만료되었을 때. |
site.published |
문서 사이트가 게시되었을 때. |
site.build_failed |
문서 사이트 게시가 실패했을 때. |
ping |
테스트 이벤트 보내기를 클릭했을 때. 구독이 필요하지 않습니다. |
다음 이벤트는 곧 출시로 나열되며 아직 전송되지 않습니다: job.progress, video.ready, article.ready, research.completed, insights.weekly.
작업 이벤트는 사용자가 앱에서 시작하는 비디오가 아니라, 에이전트가 (API 토큰이나 연결된 앱을 통해) 시작하는 작업을 다룹니다.
Clevera 전송 내용
모든 전송은 JSON 본문과 다음 헤더를 포함하는 POST입니다:
| 헤더 | 값 |
|---|---|
Content-Type |
application/json |
User-Agent |
Clevera-Webhooks/1.0 |
Clevera-Event |
job.succeeded와 같은 이벤트 이름 |
Clevera-Delivery |
전송의 고유 ID. 재전송 시 동일한 ID가 반복되므로 이를 사용하여 중복을 무시하세요. |
Clevera-Signature |
t=<unix time>,v1=<signature> |
페이로드에는 ID, 링크, 상태 및 숫자가 포함되며 콘텐츠 자체는 포함되지 않습니다. 완료된 비디오나 아티클을 읽으려면 토큰을 사용하여 API를 호출하세요.
{
"id": "6f1c2d3e-4b5a-4c6d-8e9f-0a1b2c3d4e5f",
"event": "job.succeeded",
"created_at": "2026-10-20T14:03:11Z",
"data": {
"job": {
"id": "job_8k2m4n6p8r0t2v4x",
"kind": "video.create",
"status": "succeeded",
"error_code": null,
"subject": { "type": "video", "id": "vid_example" },
"credits_spent": 64
},
"needs_input": null,
"workspace": { "id": "tm_example" }
}
}
{
"id": "0b9a8c7d-6e5f-4a3b-9c2d-1e0f9a8b7c6d",
"event": "approval.requested",
"created_at": "2026-10-20T14:01:02Z",
"data": {
"approval": {
"id": "apr_example",
"kind": "spend",
"status": "pending",
"credits_high": 80,
"expires_at": "2026-10-21T14:01:02Z",
"decided_at": null,
"url": "https://app.clevera.ai/agents?tab=requests&team=tm_example"
},
"job": { "id": "job_8k2m4n6p8r0t2v4x", "kind": "video.create" },
"workspace": { "id": "tm_example" }
}
}
서명 검증
모든 전송은 신뢰하기 전에 확인하세요. 서명은 HMAC-SHA256 방식이며, 서명 시크릿을 키로 사용하여 타임스탬프, 점(.), 그리고 원본 요청 본문을 조합한 것입니다: <t>.<body>.
Clevera-Signature헤더에서t와v1를 읽습니다.HMAC-SHA256(secret, t + "." + rawBody)를 16진수로 계산합니다.- 상수 시간 비교를 통해
v1와 비교합니다. - 캡처된 요청이 재사용되지 않도록
t가 5분 이상 지난 전송은 거부합니다.
import crypto from 'node:crypto';
export function verifyClevera(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
const t = Number(parts.t);
if (!parts.v1 || !Number.isInteger(t)) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
import hashlib, hmac, time
def verify_clevera(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(","))
t, v1 = parts.get("t", ""), parts.get("v1", "")
if not t.isdigit() or not v1 or abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
function verifyClevera(string $rawBody, string $header, string $secret, int $tolerance = 300): bool
{
parse_str(str_replace(',', '&', $header), $parts);
$t = $parts['t'] ?? '';
$v1 = $parts['v1'] ?? '';
if (! ctype_digit($t) || $v1 === '' || abs(time() - (int) $t) > $tolerance) {
return false;
}
return hash_equals(hash_hmac('sha256', $t.'.'.$rawBody, $secret), $v1);
}
수신된 본문을 그대로 사용하세요. JSON을 먼저 파싱하고 다시 인코딩하면 바이트가 변경되어 서명이 일치하지 않게 됩니다.
재시도 및 실패
엔드포인트가 2xx 상태 코드로 응답하면 전송된 것으로 간주합니다. 그 외의 모든 응답은 시도 실패로 처리됩니다:
- 리디렉션을 포함한 다른 모든 상태 코드(
3xx은 따르지 않음); - 10초 이내에 응답 없음(연결에 5초 소요);
- 더 이상 공용 인터넷에서 확인되지 않는 주소.
Clevera는 실패한 전송을 1분, 5분, 30분, 2시간, 6시간 후 총 5번 재시도합니다. 엔드포인트의 응답 본문은 읽지 않습니다.
15번 연속으로 전송이 실패하면 웹훅이 꺼집니다: "15번 연속 전송 실패로 꺼졌습니다. 엔드포인트를 수정한 후 켜세요." 소유자와 관리자에게 "host에 대한 웹훅이 꺼졌습니다"라는 알림이 전송됩니다: "전송이 계속 실패했습니다. 엔드포인트를 수정한 후 다시 켜세요." 꺼져 있는 동안 발생한 이벤트는 전송되지 않습니다. 테스트 이벤트는 이 횟수에 포함되지 않습니다.
웹훅 관리
웹훅에서 전송을 클릭하여 로그를 확인하거나, ⋯ 메뉴를 열어 다음 작업을 수행하세요:
| 옵션 | 기능 |
|---|---|
| 편집 | URL, 이벤트 또는 설명을 변경합니다. |
| 전송 | 상태(전송됨, 실패, 전송 중), 시도 횟수 및 HTTP 코드가 포함된 최근 100건의 전송 내역입니다. 재전송을 통해 다시 보낼 수 있습니다. 전송 내역은 30일간 보관됩니다. |
| 테스트 이벤트 보내기 | 서명된 ping을 보냅니다. |
| 시크릿 다시 생성 | "새 시크릿으로 즉시 서명됩니다. 엔드포인트를 업데이트하여 확인하세요." 중첩 기간이 없으므로 즉시 엔드포인트를 업데이트하세요. |
| 끄기 / 켜기 | 전송을 일시 중지하거나 재개합니다. |
| 삭제 | "이벤트 전송을 중단하고 전송 로그를 삭제합니다. 이 작업은 되돌릴 수 없습니다." |
워크스페이스가 Pro 플랜에서 해지될 경우
웹훅은 목록에 유지되며 편집 및 삭제가 가능하지만, 아무것도 전송되지 않습니다. 재시도를 기다리는 전송은 plan_locked으로 실패 처리되며, 테스트 이벤트 보내기 및 재전송 시 "웹훅이 플랜에 포함되어 있지 않습니다"라는 메시지가 표시됩니다. Pro 플랜으로 다시 업그레이드하면 전송이 재개되며, 삭제된 항목은 없습니다.
문제 해결
| 메시지 | 원인 | 해결 방법 |
|---|---|---|
| "웹훅이 플랜에 포함되어 있지 않습니다." | 워크스페이스가 Pro, Business 또는 Custom 플랜이 아님. | Pro 플랜으로 업그레이드하세요. |
| "https:// 주소를 사용하세요." | URL이 http://으로 시작함. |
HTTPS를 통해 엔드포인트를 제공하세요. |
| "IP 주소가 아닌 호스트 이름을 사용하세요." | URL이 IP 주소를 사용함. | 도메인 이름을 사용하세요. |
| "이 주소는 공용 인터넷에서 연결할 수 없습니다." | 호스트가 사설 또는 로컬 주소로 확인됨. | 공용 엔드포인트나 릴레이를 사용하세요. |
| "주소에서 사용자 이름과 비밀번호를 제거하세요." | URL에 user:password@이 포함됨. |
제거하고 대신 서명을 확인하세요. |
| "유효한 웹 주소를 입력하세요." | URL이 유효하지 않음. | 오타를 확인하세요. |
| "최소 하나의 이벤트를 선택하세요." | 선택된 이벤트가 없음. | 최소 하나의 이벤트를 선택하세요. |
| "이 워크스페이스에는 이미 10개의 웹훅이 있습니다." | 워크스페이스 제한 도달. | 사용하지 않는 웹훅을 삭제하세요. |
| "먼저 웹훅을 켜세요." | 꺼진 웹훅에서 재전송을 시도함. | 웹훅을 켠 후 재전송하세요. |
| "이 전송은 아직 전송 중입니다." | 전송이 재시도 중임. | 완료될 때까지 기다리세요. |
| 전송이 HTTP 코드로 실패함 | 엔드포인트가 오류로 응답함. | 엔드포인트 로그를 확인하고 2xx를 빠르게 반환하는지 확인하세요. 느린 작업은 응답 후에 수행하세요. |
| 서명이 일치하지 않음 | 확인 전 본문이 변경되었거나 시크릿이 오래됨. | 원본 본문으로 검증하고, 확실하지 않으면 시크릿을 다시 생성하세요. |