01
키 없는 재전송은 두 번째 청구서입니다
Idempotency-Key가 있으면 24시간 안의 같은 모델·입력·콜백 재전송은 자금을 두 번 묶지 않고 원래 작업을 돌려줍니다. 범위는 API 키 하나이며, 형제 키가 같은 값을 재생하면 409만 받고 작업 ID는 받지 못합니다.
구현 가이드

해결 과제
제출부터 파일을 손에 쥘 때까지 작업을 잃을 곳이 넷 있습니다. 타임아웃 뒤 생각 없이 다시 보내는 생성, 검증할 수 없는 콜백, 두들기거나 포기하는 폴링 루프, 그리고 어떻게 처리할지 고민하는 사이에 만료된 결과 URL입니다.
01
Idempotency-Key가 있으면 24시간 안의 같은 모델·입력·콜백 재전송은 자금을 두 번 묶지 않고 원래 작업을 돌려줍니다. 범위는 API 키 하나이며, 형제 키가 같은 값을 재생하면 409만 받고 작업 ID는 받지 못합니다.
02
전달에는 X-Webhook-Timestamp와, taskId.timestamp.sha256(raw_body)로 계산한 Base64 HMAC-SHA256이 X-Webhook-Signature에 실립니다. 원시 바이트를 대상으로 상수 시간 비교하고, 오래된 타임스탬프는 작업이나 과금 상태를 건드리기 전에 거부하세요.
03
recordInfo는 보조 수단이지 전송 계층이 아닙니다. 2초쯤에서 시작해 매번 1.5배로 늘리고 15초에서 멈추며, 네트워크가 아니라 여러분이 정한 기한에서 끝내세요.
구현 방식
멱등 키, taskId, 자체 상관 ID를 호출자에게 응답하는 것과 같은 트랜잭션에서 저장하세요. 이후의 콜백, 폴링, 재시도, 문의는 모두 그 한 행으로 귀결되고, 중복 전달은 두 번째 주문이 아니라 한 번의 갱신이 됩니다.
먼저 검증하고 그다음에 읽으세요. 서명은 원시 바이트를 덮으므로 비교가 통과한 뒤에 파싱합니다.
Verify the callback before you trust it
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
// rawBody is the exact bytes that arrived. Parse only to read the task ID:
// the digest is always computed over those raw bytes, never over a re-encode.
export function verifyDelivery(rawBody, headers, webhookKey) {
const timestamp = headers["x-webhook-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const taskId = JSON.parse(rawBody).data.taskId; // payload version 2
const digest = createHash("sha256").update(rawBody).digest("hex");
const expected = createHmac("sha256", webhookKey)
.update(`${taskId}.${timestamp}.${digest}`)
.digest("base64");
const sent = Buffer.from(headers["x-webhook-signature"] ?? "", "utf8");
const mine = Buffer.from(expected, "utf8");
return sent.length === mine.length && timingSafeEqual(sent, mine);
}Delivery headers
X-Webhook-Timestamp: 1788652800
X-Webhook-Signature: <base64>
X-Webhook-Payload-Version: 2
{
"code": 200,
"msg": "success",
"request_id": "whd_01k3m8x9q2z4v7n5p6r8s0t1w3",
"data": { "taskId": "job_01k3…", "state": "succeeded", "settled": true, … }
}계약
| 필드 또는 헤더 | 값 또는 상한 |
|---|---|
| Idempotency-Key | 24 h · scoped to one API key |
| X-Webhook-Signature | base64(HMAC-SHA256) |
| signed string | taskId.timestamp.sha256(raw_body) |
| X-Webhook-Payload-Version | 2 for new integrations |
좌우로 밀어 모든 항목을 확인하세요.
연동
적합성
근거
/api/v1의 모든 동작에서 봉투는 {code, msg, data, request_id}입니다. 페이로드 버전 2에서는 전달의 request_id가 재전송 사이에도 유지되므로 그대로 중복 제거 키가 됩니다.
settled: false인 cost는 묶어 둔 견적액이고, settled: true가 최종 청구입니다. 성공해도 보류액을 넘지 않으며, 실패나 만료는 전액 해제합니다.
errorCode는 아홉 개의 안정된 값 가운데 하나입니다. invalid_request, unsupported_combination, content_rejected, rate_limited, upstream_unavailable, generation_failed, timeout, invalid_asset, upstream_failed. 그 밖의 값은 upstream_failed로 처리하세요.
출시 전
자주 묻는 질문
모델
각 링크는 해당 모델 계열 페이지로 이동합니다. 입력, 상한, 콘텐츠 방침, 단위당 현재 가격을 그곳에서 확인할 수 있습니다.
다음
오래 걸리는 미디어 작업을 서버에서 보내고 확실히 끝내기. 멱등 키, 서명된 콜백, 상한을 둔 폴링, 다섯 상태의 상태 기계.