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

# Callback de Conclusão de Tarefa (Webhook)

> Inclua uma URL de callback ao enviar uma tarefa de geração assíncrona e enviaremos o resultado via POST assim que a tarefa terminar (sucesso ou falha) — sem necessidade de polling.

Ao enviar tarefas de geração assíncrona como vídeo / imagem / áudio, você pode incluir uma URL de callback. **Assim que a tarefa termina (com sucesso ou falha)**, enviaremos ativamente o resultado via POST para a sua URL, para que você não precise ficar consultando o tempo todo.

## Início rápido

Ao enviar uma tarefa, adicione um campo `webhook` ao corpo da requisição:

```bash theme={null}
curl -X POST https://seu-dominio-de-acesso/v1/images/generations \
  -H "Authorization: Bearer SUA_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"
  }'
```

Depois que a tarefa terminar, enviaremos uma requisição POST para **`sua URL + /callback`**.

<Note>
  Outros endpoints de tarefas assíncronas (vídeo, áudio, etc.) funcionam da mesma forma — basta adicionar o campo `webhook` ao corpo da requisição.
</Note>

## Regras de URL

O `webhook` que você fornece é a **URL base**, à qual anexamos automaticamente `/callback`:

| Seu `webhook`                  | Para onde realmente enviamos o 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` |

Portanto, seu servidor precisa de um endpoint que aceite `POST .../callback`.

## O que você vai receber

O conteúdo enviado é **exatamente igual ao que o endpoint "[Obter status da tarefa](/pt/api-reference/tasks/status)" retorna** — você pode processá-lo com a mesma lógica de análise.

<CodeGroup>
  ```json Sucesso (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 Falha (status: failed) theme={null}
  {
    "id": "task_xxx",
    "status": "failed",
    "progress": 100,
    "created": 1781589029,
    "completed": 1781589050,
    "error": { "message": "motivo da falha...", "code": "" }
  }
  ```
</CodeGroup>

<Note>
  Para tarefas de vídeo, o resultado fica em `result.videos`, e para áudio em `result.audios`.
</Note>

<Warning>
  Só enviamos quando uma tarefa atinge um **estado final** (`completed` / `failed`); não enviamos enquanto está em processamento.
</Warning>

## Novas tentativas e deduplicação (importante)

* **Novas tentativas**: Se o seu servidor não retornar `2xx` em cerca de 10 segundos, ou retornar `5xx`, tentaremos novamente de forma automática, até **3 vezes**, em intervalos de aproximadamente **10s, 30s e 60s**. Se as 3 falharem, desistimos (em cerca de 2 minutos).
* **Sem nova tentativa**: Se o seu endpoint retornar `4xx` (considerado URL / requisição com problema), desistimos imediatamente, sem tentar de novo.
* **Deduplicação**: Normalmente uma tarefa é enviada apenas uma vez. Mas em casos extremos (ex.: um reinício do nosso lado após o envio, mas antes da confirmação) você **pode receber envios duplicados**. Certifique-se de **deduplicar de forma idempotente por `id` (task\_id)** para evitar processamento em duplicidade.

**Recomendações para o seu endpoint receptor:**

<Steps>
  <Step title="Retorne 2xx o quanto antes">
    Aceite e enfileire primeiro, depois processe de forma assíncrona — não nos faça esperar até você terminar o processamento.
  </Step>

  <Step title="Deduplique por id">
    Use `id` (task\_id) como chave de idempotência para evitar processamento em duplicidade.
  </Step>

  <Step title="Configure e verifique a assinatura">
    Em produção, verifique a origem das requisições de callback e rejeite as falsificadas.
  </Step>
</Steps>

## Requisitos para a URL de callback

Por segurança, a URL de callback deve atender a:

| Requisito              | Descrição                                                                                             |
| ---------------------- | ----------------------------------------------------------------------------------------------------- |
| Acessível publicamente | **Não pode** ser um endereço interno / local (ex.: `127.0.0.1`, `10.x`, `192.168.x` serão rejeitados) |
| Protocolo              | `http` ou `https` (`https` recomendado)                                                               |
| Porta                  | Use portas padrão (`80` / `443`); portas não padrão podem ser bloqueadas                              |
| Domínio                | Não pode apontar para o nosso próprio domínio de serviço                                              |

URLs que não atendem a esses requisitos são descartadas (sem envio nem nova tentativa).

## Perguntas frequentes

<AccordionGroup>
  <Accordion title="Enviei uma tarefa com webhook, mas não recebi nenhum envio?">
    Verifique item por item:

    1. A tarefa **realmente terminou**? Consulte os detalhes da tarefa — o `status` está `completed` / `failed` (não há envio durante o processamento)?
    2. Sua URL está **acessível publicamente**? Conseguimos alcançar o seu `/callback`?
    3. A porta é padrão (80 / 443)? Portas não padrão podem ser bloqueadas por políticas de segurança.
    4. Seu `/callback` **retornou 2xx a tempo**? Retornar 4xx é descartado imediatamente.
    5. Você está usando `https`? O certificado é válido?
  </Accordion>

  <Accordion title="Por que url no result é um array?">
    Alguns modelos produzem várias imagens de uma vez, então `images[].url` pode ser um array — basta tratá-lo como um array.
  </Accordion>

  <Accordion title="Os links de resultado expiram?">
    Se `result` incluir `expires_at` (timestamp Unix), ele indica a hora de expiração do link — transfira/salve-o a tempo.
  </Accordion>

  <Accordion title="Vocês enviam o status 'em processamento'?">
    Não. Enviamos apenas uma vez, quando a tarefa finalmente tem sucesso ou falha.
  </Accordion>
</AccordionGroup>

## Exemplo mínimo de receptor

```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("Callback de tarefa recebido:", data["id"], data["status"])
        # TODO: deduplicar por id, verificar a assinatura e então processar
        self.send_response(200); self.end_headers()
        self.wfile.write(b'{"ok":true}')

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

Retorne `200` o quanto antes e execute sua lógica de processamento de forma assíncrona em segundo plano.
