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

# Webhook

> 전달 페이로드, 재시도 일정, 안전한 핸들러 작성법.

태스크에 `webhook.url`을 지정하면, 태스크가 종료 상태(`COMPLETED` 또는 `FAILED`)에 도달했을 때
결과를 그 주소로 POST합니다.

```json theme={null}
{
  "taskType": "PERPLEXITY",
  "payload": { "prompt": "best wireless earbuds 2026" },
  "webhook": { "url": "https://your-server.example.com/hook" }
}
```

## 전달 페이로드

`Content-Type: application/json`으로 다음 본문을 POST합니다.

<ResponseField name="task" type="object" required>
  제출 시 받은 것과 같은 태스크 메타데이터입니다. 이제 종료 `status`를 갖고 있고, 보낸
  `idempotencyKey`도 함께 돌아옵니다.
</ResponseField>

<ResponseField name="credits" type="object" required>
  엔진별 크레딧 비용입니다. Perplexity는 `2`, ChatGPT는 `3` 같은 식입니다(엔진에 따라 1–3
  크레딧). 태스크가 실패하면 `creditsCharged`는 `0`입니다. 항상 0을 보고하는 제출·폴링 응답과
  달리 이 필드는 0이 아닌 값을 가집니다.
</ResponseField>

<ResponseField name="response" type="object" required>
  엔진별 결과입니다. [엔진](/ko/engines/overview)을 참고하세요. 실패한 태스크에서는 엔진 결과 대신
  `{ "error": "<reason>" }`이 들어갑니다.
</ResponseField>

```json theme={null}
{
  "task": {
    "id": "8f2c1e40-...",
    "taskType": "PERPLEXITY",
    "status": "COMPLETED",
    "priority": 1,
    "createdAt": "2026-07-09T04:12:00.000Z",
    "idempotencyKey": "job-42:prompt-7:perplexity"
  },
  "credits": { "creditsToCharge": 3, "creditsCharged": 3 },
  "response": {
    "text": "The best wireless earbuds in 2026 are ...",
    "sources": [{ "position": 1, "url": "https://...", "label": "..." }]
  }
}
```

실패한 태스크도 전달됩니다.

```json theme={null}
{
  "task": { "id": "8f2c1e40-...", "status": "FAILED", "...": "..." },
  "credits": { "creditsToCharge": 3, "creditsCharged": 0 },
  "response": { "error": "ENGINE_TIMEOUT: The engine did not answer before the task deadline." }
}
```

## 재시도

수신 확인은 `2xx` 아무 코드나 반환하면 됩니다. 그 밖의 응답은 타임아웃을 포함해 모두 실패로
보고 지수 백오프로 재시도합니다.

| 시도 | 발송 시점 |
| - | - |
| 1 | 즉시 |
| 2 | 2분 후 |
| 3 | 4분 후 |
| 4 | 8분 후 |
| 5 | 16분 후 |

다섯 번째 시도 뒤에는 전달을 포기하고 기록만 남깁니다. 조회할 수 있는 dead-letter 큐는 없습니다.
태스크 API로 결과를 가져오려면 24시간 공개 폴링 기간 안에 폴링하세요. 이 기간은 원본 기록의 저장
기간과는 무관합니다.

시도마다 요청 타임아웃은 **30초**입니다.

<Warning>
  태스크 입장에서 전달은 보내고 잊는 방식입니다. 태스크는 완료됐지만 webhook을 끝내 전달하지
  못했더라도 `GET /v1/async/task/{id}`에서는 `COMPLETED`로 보입니다. 상태는 알림이 아니라 엔진
  작업을 나타냅니다.
</Warning>

## 안전한 핸들러 작성

<Steps>
  <Step title="빠르게 수신 확인">
    페이로드를 영속적으로 큐에 넣었다면 즉시 `200`을 반환하세요. 파싱과 DB 쓰기는 그 뒤에
    합니다. 느린 핸들러는 30초 타임아웃을 소진해 원치 않는 재시도를 부릅니다.
  </Step>

  <Step title="idempotencyKey로 중복 제거">
    재시도 때문에 핸들러가 같은 태스크를 여러 번 받는 일은 정상입니다. `task.idempotencyKey`
    (또는 `task.id`)로 매칭하고 쓰기를 멱등하게 만드세요.
  </Step>

  <Step title="존재 여부가 아니라 status로 분기">
    `task.status === "FAILED"`를 명시적으로 확인하세요. 실패한 태스크도 webhook을 전달합니다.
  </Step>
</Steps>

<Note>
  Webhook 요청에는 서명이나 공유 비밀이 없습니다. 엔드포인트가 공개되어 있다면 페이로드를 신뢰하지
  말고, 추측하기 어려운 URL 경로를 쓰거나 네트워크 수준 제한 뒤에 핸들러를 두세요.
</Note>
