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

# Webhooks

> Receive APIPod image and video task completion callbacks.

Add `callback_url` to an image or video generation request to receive a POST request when the task reaches `completed` or `failed`.

```json theme={null}
{
  "model": "gpt-image-2",
  "prompt": "A technical cutaway illustration of a compact camera",
  "callback_url": "https://api.example.com/webhooks/apipod/opaque-route-token"
}
```

## Callback request

APIPod sends `Content-Type: application/json` and `User-Agent: APIPod-Callback/1.0`.

```json theme={null}
{
  "task_id": "img_example_task_id",
  "request_id": "req_example_request_id",
  "status": "completed",
  "result": [
    "https://example.com/generated-image.png"
  ],
  "created_at": "2026-08-10T09:00:00Z",
  "completed_at": "2026-08-10T09:01:30Z"
}
```

A failed task uses the same envelope:

```json theme={null}
{
  "task_id": "img_example_task_id",
  "request_id": "req_example_request_id",
  "status": "failed",
  "error": "Upstream request failed. Please retry later.",
  "error_code": "UPSTREAM_CAPACITY_EXHAUSTED",
  "created_at": "2026-08-10T09:00:00Z",
  "completed_at": "2026-08-10T09:01:30Z"
}
```

`error_code` is optional. Do not infer success from its absence; use `status`.

## Delivery behavior

* Any HTTP status from `200` through `299` acknowledges the callback.
* Non-2xx responses and network errors are retried.
* APIPod currently makes up to five delivery attempts with exponential backoff.
* Duplicate delivery is possible, so receivers must be idempotent.
* Callback delivery is asynchronous and does not change the task's terminal state.

## Secure the receiver

<Warning>
  The current public callback contract does not include a signature header. Do not claim that a callback is authenticated solely because its JSON shape looks correct.
</Warning>

* Use HTTPS and a high-entropy, unguessable token in the callback path.
* Keep the callback URL server-side; do not expose it in client applications.
* Match `task_id` and `request_id` against tasks your system created.
* Store a processed-event key and make duplicate callbacks return the same successful outcome.
* Validate field types and reject unexpectedly large bodies.
* Return 2xx only after the callback has been durably accepted; process slow work asynchronously.
* If authenticity is critical, query the authenticated status endpoint before applying irreversible business actions.

## Minimal receiver

```javascript theme={null}
import express from "express";

const app = express();
app.use(express.json({ limit: "64kb" }));

app.post("/webhooks/apipod/:token", async (req, res) => {
  if (req.params.token !== process.env.APIPOD_WEBHOOK_TOKEN) {
    return res.sendStatus(404);
  }

  const { task_id, request_id, status } = req.body;
  if (!task_id || !request_id || !["completed", "failed"].includes(status)) {
    return res.sendStatus(400);
  }

  await persistIdempotently({ task_id, request_id, payload: req.body });
  return res.sendStatus(204);
});
```
