웹훅

작업 종료 알림과 서명 검증입니다.

무엇을 보내나

생성 작업(이미지, 영상, 오디오)이 끝나면 등록한 주소로 알림을 보냅니다. 폴링 없이 종료를 알 수 있습니다.
이벤트시점
job.succeeded작업이 성공으로 끝남
job.failed작업이 실패로 끝남
본인이 직접 취소한 작업은 알림을 보내지 않습니다. 환불이나 재시도 같은 세부 사유는 모두 job.failed로 합쳐지므로, 자세한 사유는 작업을 조회해 확인합니다.

등록하기

설정의 웹훅 화면(/settings/developer-webhooks)에서 등록합니다. 주소와 받을 이벤트를 하나 이상 고릅니다. 이 화면은 로그인한 사람이 씁니다. 개인 키로 부르는 웹훅 관리 경로는 없습니다.
  • 주소는 https만 쓸 수 있습니다.
  • 계정당 10개까지 등록할 수 있습니다.
  • 사설망과 내부 주소는 등록할 때와 보낼 때 모두 거부합니다. 리다이렉트는 따라가지 않습니다.
  • 비밀값(whsec_로 시작)은 만든 직후 한 번만 보입니다. 서명 검증에 필요하므로 안전한 곳에 둡니다. 이후에는 끝 4자리 힌트만 보입니다.
  • 주소를 지우면 아직 가지 않은 알림은 보내는 시점에 dead가 됩니다.

요청 형식

POST로 JSON 본문을 보냅니다. 결과 링크는 담지 않으며, 수신 쪽이 작업 조회 경로로 결과를 읽습니다.
알림 본문 — json
{
"id": "<이벤트 id>",
"type": "job.succeeded",
"createdAt": "2026-01-01T00:00:00+09:00",
"data": {
"jobId": "<작업 id>",
"kind": "image",
"status": "succeeded",
"documentId": null
}
}
kind는 image, video, audio 중 하나입니다. status는 succeeded 또는 failed이고 type과 짝입니다. documentId는 작업이 캔버스 문서에 속할 때만 값이 있고 아니면 null입니다. 이벤트 id는 <kind>:<작업 id>:<status> 모양이라 같은 종료는 항상 같은 id입니다.
받은 알림으로 결과를 읽을 때는 kind에 맞춰 GET /v1/images/<jobId>, GET /v1/videos/<jobId>, GET /v1/audio/<jobId>를 부릅니다.

헤더

헤더뜻
Mellowz-Signaturev1=<서명>. 회전 유예 중에는 v1=<서명>,v1=<서명>
Mellowz-Timestamp서명 시각(초)
Mellowz-Event-Id이벤트 id. 본문의 id와 같습니다
Mellowz-Event-Typejob.succeeded 또는 job.failed
본문 형식은 Content-Type: application/json입니다.

서명 검증

서명은 <타임스탬프>.<본문 원문>의 HMAC-SHA256(16진수)입니다. 비밀값 회전 직후 24시간 동안은 이전 비밀값의 서명이 하나 더 붙어 v1=<서명>,v1=<서명> 형태가 됩니다. 이 중 하나라도 맞으면 통과시킵니다.
검증은 파싱하기 전의 원문 본문으로 합니다. 시각이 5분 넘게 어긋난 요청은 재전송 공격으로 보고 거절합니다.
import crypto from "node:crypto";
​
export function verifyWebhook(secret, headers, rawBody) {
const timestamp = headers["mellowz-timestamp"];
const signatures = String(headers["mellowz-signature"] ?? "")
.split(",")
.map((part) => part.trim().replace(/^v1=/, ""));
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
return signatures.some(
(sig) => sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)),
);
}

받는 쪽 규칙

  • 2xx를 돌려주면 성공으로 기록합니다. 응답을 10초 안에 주세요.
  • 이벤트 id(Mellowz-Event-Id)가 같은 알림은 중복 전달될 수 있으니 id로 중복을 걸러 냅니다.
  • 알림은 종료를 알릴 뿐입니다. 성공 여부와 결과는 작업을 조회해 확인하세요.

전달과 재시도

상황동작
2xx전달 완료(sent)
408, 429, 5xx, 연결 실패재시도합니다. 간격은 30초부터 두 배씩 늘어납니다.
그 밖의 4xx, 3xx재시도해도 같으므로 바로 dead로 둡니다. 리다이렉트는 따라가지 않습니다.
시도 10회 소진dead
응답이 10초를 넘음연결 실패와 같이 재시도합니다.
주소가 사설 주소로 바뀜접속하지 않고 dead
간격은 30초에 2의 거듭제곱을 곱한 값입니다(30초, 60초, 120초…).
dead가 된 전달은 웹훅 화면의 전달 기록에서 다시 보내기로 같은 알림을 되살릴 수 있습니다. 시도 횟수가 0으로 돌아가 다시 10번까지 시도합니다. dead가 아닌 전달은 다시 보낼 수 없습니다. 전달 기록에는 주소마다 최근 50건이 보이고 상태(pending, sent, dead), 시도 횟수, 마지막 HTTP 상태와 오류가 있습니다.

비밀값 회전

화면에서 회전하면 새 비밀값이 한 번 보이고, 이전 비밀값도 24시간 동안 서명에 함께 쓰입니다. 수신 쪽이 새 비밀값으로 갈아탈 시간을 줍니다.

관련