웹훅
클라우드 전용
웹훅은 클라우드 버전에서만 사용할 수 있습니다.
웹훅을 사용하면 ADHDev Cloud가 머신 및 에이전트 이벤트를 자체 HTTP 엔드포인트로 전송할 수 있습니다.
현재 주요 동작:
- 웹훅 API는 운영 중입니다.
- 전용
/webhooks대시보드 페이지는 현재 안정적인 표준 탐색 서피스가 아닙니다. - 페이로드, 이벤트, 전달 히스토리 구조의 정식 계약은 REST API 레퍼런스를 사용하세요.
- 웹훅 생성/토글/삭제/테스트 작업은 최근 로그인이 보호하는 클라우드 계정 작업으로, API 키가 아닌 신선한 대시보드 세션 JWT가 필요합니다.
웹훅 생성
현재 로그인된 클라우드 대시보드 세션에서 웹훅을 생성하세요. 요청 본문은 다음과 같습니다:
{
"url": "https://example.com/hooks/adhdev",
"events": ["agent:status", "agent:approve_request"]
}만료된 브라우저 세션이나 API 키로 변경 웹훅 엔드포인트를 호출하면 서버는 훅을 프로비저닝하는 대신 403 RECENT_LOGIN_REQUIRED를 반환합니다.
생성 응답에는 다음이 포함됩니다:
- 웹훅 레코드
- 서명 검증을 위해 한 번만 표시되는
secret
events는 자유 형식이 아닌 허용 목록입니다: 아래 이름(및 "*")만 허용되며, 그 외는 조용히 무시됩니다. 개별 구독이 불가능한 세부 이벤트까지 모두 받으려면 ["*"]를 사용하세요.
이벤트
개별 구독 가능(events에서 허용):
| 이벤트 | 트리거 |
|---|---|
daemon:connect | 데몬이 서버에 연결됨 |
daemon:disconnect | 데몬 연결이 끊김 |
agent:status | 에이전트 상태 변경(스로틀됨, 데몬당 10초에 최대 1회) |
agent:approve_request | 예약됨 — 허용 목록에는 있지만 서버가 현재 발행하지 않음 |
서버가 발행하지만 "*"로 구독한 웹훅에만 전달됨(개별 구독 불가):
| 이벤트 | 트리거 |
|---|---|
agent:generating_started | 에이전트가 응답 생성을 시작함 |
agent:generating_completed | 에이전트가 생성을 완료함 |
agent:waiting_approval | 에이전트가 승인을 기다리는 중 |
agent:waiting_choice | 에이전트가 다중 선택 프롬프트를 기다리는 중 |
agent:stopped | 에이전트 세션이 중지됨 |
monitor:no_progress | 에이전트가 진행 없이 정체된 것으로 보임(레거시 별칭: monitor:long_generating) |
webhook:test | POST /webhooks/{id}/test로 명시적으로 요청된 테스트 전달 — 구독 필터링을 완전히 우회하며 테스트 대상 웹훅 하나에만 전달됨 |
페이로드 형식
모든 전송은 최상위 필드 세 개 — event, payload, timestamp(서버가 전송한 시각, epoch 밀리초) — 로 이루어진 JSON 본문입니다. 에이전트·모니터 이벤트(agent:status를 제외한 agent:*, 그리고 monitor:no_progress)의 페이로드는 다음과 같습니다:
{
"event": "agent:generating_completed",
"payload": {
"daemonId": "8e1f4c2a9b7d3e5f6a0c1b2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f",
"daemonType": "adhdev-daemon",
"event": "agent:generating_completed",
"timestamp": 1714000000000,
"targetSessionId": "9b2d7c4e-1f3a-4b5c-8d6e-0a1b2c3d4e5f",
"providerType": "claude-cli",
"providerSessionId": "3f2a9c1e-5b7d-4e8a-9c0f-1a2b3c4d5e6f",
"workspaceName": "/home/dev/projects/myproject",
"duration": 42,
"surfaceHidden": false,
"muted": false
},
"timestamp": 1714000000215
}| 필드 | 포함 조건 |
|---|---|
daemonId | 항상 — 이벤트를 보고한 데몬 연결 |
event, timestamp | 항상 — 이벤트 이름과 데몬이 관측한 시각(epoch 밀리초) |
targetSessionId | 특정 에이전트 세션에 관한 이벤트일 때 — agent:generating_completed와 agent:stopped에는 항상 포함 |
providerType | 데몬이 세션을 찾을 수 있을 때. 에이전트 프로바이더(예: claude-cli, cursor). 찾지 못하면 필드가 없으며, 데몬 종류를 담지 않습니다 |
daemonType | 항상. 이벤트를 보낸 데몬의 종류, 현재 adhdev-daemon |
providerSessionId | 프로바이더가 자체 대화 ID를 제공할 때 |
workspaceName | 세션에 워크스페이스가 있을 때 — CLI 에이전트의 작업 디렉터리 또는 IDE 워크스페이스 |
duration | agent:generating_completed에만, 데몬이 턴 시작을 관측했을 때 — 정수 초 단위 |
elapsedSec | monitor:no_progress — 진행이 없었던 시간(초) |
modalMessage, modalButtons | agent:waiting_approval / agent:waiting_choice — 승인 요청 문구와 버튼 레이블 |
surfaceHidden, muted | 데몬이 세션을 호스팅할 때 — 대시보드에서 숨김/음소거 상태인지 여부 |
daemon:connect, daemon:disconnect, agent:status, webhook:test는 각자 다른 형태의 페이로드를 보냅니다. modalMessage의 승인 요청 문구를 제외하면, 페이로드에는 채팅 메시지, 채팅 제목, 에이전트 응답이 포함되지 않습니다.
서명 검증
각 웹훅 요청에는 다음이 포함됩니다:
X-ADHDev-SignatureX-ADHDev-Event
서명 형식:
X-ADHDev-Signature: t=1714000000000,v1=a3f9c2b1...원시 요청 본문을 기준으로 검증하세요.
import crypto from 'crypto'
function verifySignature(body, signature, secret) {
const parts = Object.fromEntries(signature.split(',').map((p) => p.split('=')))
const timestamp = parts.t
const received = parts.v1
const payload = `${timestamp}.${body}`
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(received),
Buffer.from(expected)
)
}
// Express 예시
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-adhdev-signature']
const body = req.body.toString('utf8')
if (!verifySignature(body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature')
}
const event = JSON.parse(body)
// 이벤트 처리...
res.sendStatus(200)
})import hmac
import hashlib
def verify_signature(body: bytes, signature: str, secret: str) -> bool:
parts = dict(p.split('=', 1) for p in signature.split(','))
timestamp = parts.get('t', '')
received = parts.get('v1', '')
payload = f"{timestamp}.{body.decode('utf-8')}".encode()
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(received, expected)
# Flask 예시
@app.route('/webhook', methods=['POST'])
def webhook():
sig = request.headers.get('X-ADHDev-Signature', '')
if not verify_signature(request.data, sig, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
# 이벤트 처리...
return '', 200웹훅 시크릿은 웹훅 생성 시 한 번만 표시됩니다 — 안전하게 저장하세요.
관리 엔드포인트
GET /api/v1/webhooks
POST /api/v1/webhooks
PATCH /api/v1/webhooks/{webhookId}
DELETE /api/v1/webhooks/{webhookId}
GET /api/v1/webhooks/{webhookId}/deliveries?limit=20
POST /api/v1/webhooks/{webhookId}/testGET 엔드포인트가 가장 안전한 자동화 서피스입니다. 변경 엔드포인트(POST, PATCH, DELETE, test)는 최근 로그인이 보호하는 대시보드 세션 작업입니다.
재시도 정책
실패한 전달은 지수 백오프로 최대 3회 재시도됩니다.
플랜 제한
| 플랜 | 최대 웹훅 수 |
|---|---|
| Free | 2 |
| Pro | 10 |
| Ultra | 50 |
| Enterprise | 무제한 |
