> ## 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 обратного вызова. **После завершения задачи (успешно или с ошибкой)** мы активно отправим результат через POST на ваш URL, чтобы вам не приходилось постоянно опрашивать статус.

## Быстрый старт

При отправке задачи добавьте поле `webhook` в тело запроса:

```bash theme={null}
curl -X POST https://ваш-домен-доступа/v1/images/generations \
  -H "Authorization: Bearer ВАШ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2",
    "prompt": "a red apple on a table",
    "size": "1024x1024",
    "webhook": "https://your-server.com"
  }'
```

После завершения задачи мы отправим POST-запрос на **`ваш URL + /callback`**.

<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`.

## Что вы получите

Отправляемое содержимое **полностью совпадает с тем, что возвращает конечная точка «[Получение статуса задачи](/ru/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>

## Повторные попытки и дедупликация (важно)

* **Повторные попытки**: Если ваш сервер не вернёт `2xx` примерно за 10 секунд или вернёт `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="Почему url в result — это массив?">
    Некоторые модели создают несколько изображений за раз, поэтому `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` как можно быстрее, а логику обработки выполняйте асинхронно в фоне.
