> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apimart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 작업 완료 콜백(Webhook)

> 비동기 생성 작업을 제출할 때 콜백 URL을 함께 전달하면, 작업 완료(성공 또는 실패) 후 결과를 POST로 보내드립니다. 폴링이 필요 없습니다.

동영상 / 이미지 / 오디오 등 비동기 생성 작업을 제출할 때 콜백 URL을 함께 전달할 수 있습니다. **작업이 완료되면(성공 또는 실패)** 결과를 여러분의 URL로 능동적으로 POST하므로, 계속 폴링하며 조회할 필요가 없습니다.

## 빠른 시작

작업 제출 시 요청 본문에 `webhook` 필드를 추가하세요:

```bash theme={null}
curl -X POST https://여러분의접속도메인/v1/images/generations \
  -H "Authorization: Bearer 여러분의APIKey" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2",
    "prompt": "a red apple on a table",
    "size": "1024x1024",
    "webhook": "https://your-server.com"
  }'
```

작업이 끝나면 **`여러분의 URL + /callback`** 으로 POST 요청을 보냅니다.

<Note>
  동영상, 오디오 등 다른 비동기 작업 엔드포인트도 동일합니다. 모두 요청 본문에 `webhook` 필드를 추가하면 됩니다.
</Note>

## URL 규칙

전달하는 `webhook` 은 **기본 URL(base)** 이며, 뒤에 자동으로 `/callback` 을 덧붙입니다:

| 여러분의 `webhook`                 | 실제로 POST하는 주소                          |
| ------------------------------ | -------------------------------------- |
| `https://your-server.com`      | `https://your-server.com/callback`     |
| `https://your-server.com/api`  | `https://your-server.com/api/callback` |
| `https://your-server.com/api/` | `https://your-server.com/api/callback` |

따라서 서버 측에서 `POST .../callback` 을 수신하는 엔드포인트를 준비해야 합니다.

## 무엇을 받게 되나요

푸시되는 내용은 **「[작업 상태 조회](/ko/api-reference/tasks/status)」 엔드포인트가 반환하는 것과 완전히 동일**합니다. 동일한 파싱 로직으로 처리하면 됩니다.

<CodeGroup>
  ```json 성공(status: completed) theme={null}
  {
    "id": "task_01KV7FXR8BEYS1BWHJCT3JMCJ5",
    "status": "completed",
    "progress": 100,
    "created": 1781589029,
    "completed": 1781589058,
    "actual_time": 29,
    "cost": 0.006,
    "credits_cost": 0.06,
    "result": {
      "images": [{ "url": ["https://.../result.png"], "expires_at": 1781675458 }]
    }
  }
  ```

  ```json 실패(status: failed) theme={null}
  {
    "id": "task_xxx",
    "status": "failed",
    "progress": 100,
    "created": 1781589029,
    "completed": 1781589050,
    "error": { "message": "실패 사유...", "code": "" }
  }
  ```
</CodeGroup>

<Note>
  동영상 작업의 결과는 `result.videos`, 오디오는 `result.audios` 에 있습니다.
</Note>

<Warning>
  작업이 **종료 상태**(`completed` / `failed`)에 도달했을 때만 푸시합니다. 처리 중에는 푸시하지 않습니다.
</Warning>

## 재시도와 중복 제거(중요)

* **재시도**: 서버가 약 10초 이내에 `2xx` 를 반환하지 않거나 `5xx` 를 반환하면, 자동으로 최대 **3회** 재시도하며 간격은 약 **10초, 30초, 60초** 입니다. 3회 모두 실패하면 포기합니다(약 2분 이내 종료).
* **재시도하지 않는 경우**: 엔드포인트가 `4xx` 를 반환하면(URL / 요청에 문제가 있다고 간주) 재시도 없이 즉시 포기합니다.
* **중복 제거**: 일반적으로 한 작업은 한 번만 푸시됩니다. 다만 극단적인 경우(예: 전송 후 확인 전에 당사 측에서 재시작 발생) **중복 푸시를 받을 수 있습니다**. 중복 처리를 방지하기 위해 반드시 **`id`(task\_id) 기준으로 멱등하게 중복 제거** 하세요.

**수신 엔드포인트 권장 사항:**

<Steps>
  <Step title="가능한 한 빨리 2xx 반환">
    먼저 수신해 큐에 넣은 뒤 비동기로 처리하세요. 처리가 끝날 때까지 당사를 기다리게 하지 마세요.
  </Step>

  <Step title="id 로 중복 제거">
    `id`(task\_id)를 멱등 키로 사용해 중복 처리를 방지하세요.
  </Step>

  <Step title="서명 구성 및 검증">
    운영 환경에서는 콜백 요청의 출처를 검증하고 위조된 요청을 거부하세요.
  </Step>
</Steps>

## 콜백 URL 요구 사항

보안을 위해 콜백 URL은 다음을 충족해야 합니다:

| 요구 사항    | 설명                                                          |
| -------- | ----------------------------------------------------------- |
| 공용 접근 가능 | 내부 / 로컬 주소(`127.0.0.1`, `10.x`, `192.168.x` 등)는 **불가**(거부됨) |
| 프로토콜     | `http` 또는 `https`(`https` 권장)                               |
| 포트       | 표준 포트(`80` / `443`) 사용. 비표준 포트는 차단될 수 있음                    |
| 도메인      | 당사 자체 서비스 도메인을 가리켜서는 안 됨                                    |

요구 사항을 충족하지 않는 URL은 즉시 폐기됩니다(푸시도 재시도도 하지 않음).

## 자주 묻는 질문

<AccordionGroup>
  <Accordion title="webhook을 포함해 작업을 제출했는데 푸시를 받지 못했나요?">
    하나씩 점검하세요:

    1. 작업이 **실제로 완료되었나요**? 작업 상세를 조회해 `status` 가 `completed` / `failed` 인지 확인하세요(처리 중에는 푸시하지 않음).
    2. 여러분의 URL이 **공용으로 접근 가능**한가요? 당사가 여러분의 `/callback` 에 연결할 수 있나요?
    3. 포트가 표준 포트(80 / 443)인가요? 비표준 포트는 보안 정책에 의해 차단될 수 있습니다.
    4. 여러분의 `/callback` 이 **제때 2xx 를 반환**했나요? 4xx 를 반환하면 즉시 포기합니다.
    5. `https` 를 사용하나요? 인증서가 유효한가요?
  </Accordion>

  <Accordion title="result 의 url 이 왜 배열인가요?">
    일부 모델은 한 번에 여러 장의 이미지를 생성하므로 `images[].url` 이 배열일 수 있습니다. 배열로 처리하면 됩니다.
  </Accordion>

  <Accordion title="결과 링크에 유효 기간이 있나요?">
    `result` 에 `expires_at`(Unix 타임스탬프)이 있으면 해당 링크의 만료 시각을 나타냅니다. 제때 옮겨 저장하세요.
  </Accordion>

  <Accordion title="'처리 중' 상태도 푸시하나요?">
    아니요. 작업이 최종적으로 성공하거나 실패할 때 한 번만 푸시합니다.
  </Accordion>
</AccordionGroup>

## 최소 수신 측 예제

```python Python theme={null}
from http.server import BaseHTTPRequestHandler, HTTPServer
import json

class H(BaseHTTPRequestHandler):
    def do_POST(self):
        n = int(self.headers.get("Content-Length") or 0)
        body = self.rfile.read(n)
        data = json.loads(body)
        print("작업 콜백 수신:", data["id"], data["status"])
        # TODO: id 로 중복 제거하고 서명을 검증한 뒤 처리
        self.send_response(200); self.end_headers()
        self.wfile.write(b'{"ok":true}')

HTTPServer(("0.0.0.0", 443), H).serve_forever()
```

수신 후 가능한 한 빨리 `200` 을 반환하고, 처리 로직은 백그라운드에서 비동기로 실행하세요.
