내 앱에 통합하기
결과 콜백
웹훅은 작업 완료와 앱이 이를 알게 되는 시점 사이의 지연을 줄입니다. 전송 최적화일 뿐이며, 세 번 시도한 뒤 게이트웨이는 더 이상 시도하지 않습니다. 기준은 언제나 상태 엔드포인트입니다.
#제출 시 콜백 등록
제출 요청에 webhook_url을 넣으세요. webhook_secret을 추가하면 모든 전송에 서명이 붙고 — 없으면 아무것도 서명되지 않습니다.
curl https://www.aisplice.store/v1/videos \
-H "Authorization: Bearer sk-YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: video-job-8842" \
-d '{
"model": "doubao-seedance-2.5",
"prompt": "A cinematic product scene",
"webhook_url": "https://your.app/hooks/ai-tasks",
"webhook_secret": "whsec_replace_me"
}'공개적으로 라우팅 가능한 주소여야 함
localhost, 127.0.0.1, 사설 대역, 링크 로컬, 클라우드 메타데이터 주소는 전송 시점이 아니라 작업 제출 시점에 거부됩니다. 개발 중에는 터널을 사용하세요.
http 또는 https, 제출 시점에 확인(resolve) 가능해야 함
호스트 이름을 확인(resolve)하고 반환된 모든 주소를 검사합니다. 확인되지 않는 이름이면 제출이 실패합니다.
전송은 best-effort 방식
세 번 재시도한 뒤에는 게이트웨이가 전송을 멈추고 로그만 남깁니다. 기준은 언제나 상태 엔드포인트입니다.
localhost는 작업 제출 시 거부됨
#헤더와 페이로드
| 헤더 | 전송 조건 | 활용 방법 |
|---|---|---|
| X-Xiaoxiang-Signature | 작업을 webhook_secret과 함께 제출한 경우에만. | sha256=<원본 본문의 HMAC-SHA256 16진수 값>. 파싱하기 전에 상수 시간 비교로 검증하세요. |
| X-Xiaoxiang-Event | 모든 전송. | task.completed 또는 task.failed — 페이로드에서 의도를 다시 추론하지 말고 이 값으로 분기하세요. |
| X-Xiaoxiang-Event-Id | 모든 전송. | 같은 이벤트가 재시도되어도 변하지 않습니다. 저장해 두고 중복은 버리세요 — 이것이 중복 제거 키입니다. |
| 필드 | 타입 | 포함 여부 | 설명 |
|---|---|---|---|
| event_id | string | 항상 | X-Xiaoxiang-Event-Id와 같습니다. 이 값으로 중복을 제거하세요. |
| event | string | 항상 | "task.completed" 또는 "task.failed". |
| task_id | string | 항상 | 제출 호출이 반환한 id — 내 시스템과 연결할 키입니다. |
| status | string | 항상 | 동영상 용어 기준의 최종 상태 — 이미지·음악 작업도 "completed" 또는 "failed"를 사용합니다. |
| model | string | 알려진 경우 | 작업을 생성한 모델입니다. 알 수 없으면 생략됩니다. |
| url | string | 성공 시 | 작업 성공 시 결과 URL입니다. 안정적인 플랫폼 링크가 필요하면 /content를 사용하세요. |
| fail_reason | string | 실패 시 | 실패 시에만 포함됩니다. |
| quota | integer | 항상 | 실제로 소비된 한도(내부 단위)입니다. 대조는 이 필드가 아닌 사용량 로그를 기준으로 하세요. |
| created_at | integer | 항상 | 작업 제출 시각(Unix 초). |
| finished_at | integer | 항상 | 최종 상태 전환 시각(Unix 초). |
{
"event_id": "task_8f21c...:task.completed",
"event": "task.completed",
"task_id": "8f21c0d2-...",
"status": "completed",
"model": "doubao-seedance-2.5",
"url": "https://cdn.example.com/renders/8f21c0d2.mp4",
"quota": 128000,
"created_at": 1789000000,
"finished_at": 1789000184
}#원본 요청 본문 검증
JSON을 파싱하기 전에, 받은 바이트 그대로 HMAC-SHA256을 계산하세요 — 페이로드를 다시 직렬화하면 바이트가 바뀌어 비교가 깨집니다. 상수 시간으로 비교하고 일치하지 않으면 거부하세요.
import crypto from "node:crypto";
// Express: express.raw({ type: "application/json" }) — not express.json().
export function handleTaskWebhook(req, res) {
const received = req.get("X-Xiaoxiang-Signature") ?? "";
const expected =
"sha256=" +
crypto.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(req.body) // Buffer of the raw body
.digest("hex");
const a = Buffer.from(received);
const b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString("utf8"));
// Deduplicate first, acknowledge fast, then do the work off-request.
if (!seen.add(req.get("X-Xiaoxiang-Event-Id"))) return res.status(200).end();
queue.push(event);
res.status(200).end();
}#전송과 중복 제거
15초 안에 응답
이것이 전송 타임아웃입니다. 더 늦으면 실패한 시도로 간주되므로, 작업은 큐에 넣고 먼저 2xx를 반환하세요.
이벤트 ID로 중복 제거
X-Xiaoxiang-Event-Id는 재시도 간에 변하지 않습니다. 비즈니스 상태를 변경하기 전에 저장해 두고 중복은 버리세요.
세 번 재시도 후에는 더 이상 오지 않음
실패한 전송은 5초, 30초, 2분 후에 재시도됩니다. 그 뒤로 게이트웨이는 로그만 남깁니다 — 더 이상 아무것도 오지 않습니다.
#대조용 폴러 유지
웹훅 전송은 best-effort 방식
아직 해결되지 않았나요?
요청 ID나 오류 메시지를 보내 주시면 확인해 드리겠습니다.