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

# Webhooks

> Receive real-time task completion notifications

Receive task results via HTTP callbacks instead of polling.

## Overview

When using `callback` mode, APIXO sends a POST request to your specified URL when a task completes. This eliminates polling overhead and provides immediate notifications — ideal for high-volume production applications.

## Setup

<Steps>
  <Step title="Create a webhook endpoint">
    Your endpoint must accept POST requests, respond with HTTP 200 within 30 seconds, and be publicly accessible via HTTPS.

    <Warning>
      If your endpoint does not respond with HTTP 200 within 30 seconds, APIXO will retry delivery up to 3 times with increasing delays.
    </Warning>

    <Tabs>
      <Tab title="Express.js">
        ```javascript theme={null}
        const express = require('express');
        const app = express();

        app.use(express.json());

        app.post('/webhook/apixo', (req, res) => {
          const { taskId, state, resultJson, failCode, failMsg } = req.body.data;
          
          if (state === 'success') {
            const urls = JSON.parse(resultJson).resultUrls;
            console.log('Generated:', urls);
          } else if (state === 'failed') {
            console.error(`Task ${taskId} failed: ${failCode} - ${failMsg}`);
          }
          
          res.status(200).send('OK');
        });

        app.listen(3000);
        ```
      </Tab>

      <Tab title="FastAPI">
        ```python theme={null}
        from fastapi import FastAPI, Request
        import json

        app = FastAPI()

        @app.post('/webhook/apixo')
        async def handle_webhook(request: Request):
            body = await request.json()
            data = body['data']
            
            if data['state'] == 'success':
                urls = json.loads(data['resultJson'])['resultUrls']
                print(f"Generated: {urls}")
            elif data['state'] == 'failed':
                print(f"Task {data['taskId']} failed: {data['failCode']} - {data['failMsg']}")
            
            return {'status': 'ok'}
        ```
      </Tab>

      <Tab title="Next.js">
        ```typescript theme={null}
        // app/api/webhook/apixo/route.ts
        import { NextRequest, NextResponse } from 'next/server';

        export async function POST(request: NextRequest) {
          const body = await request.json();
          const { taskId, state, resultJson, failCode, failMsg } = body.data;
          
          if (state === 'success') {
            const urls = JSON.parse(resultJson).resultUrls;
            console.log('Generated:', urls);
          } else if (state === 'failed') {
            console.error(`Task ${taskId} failed: ${failCode} - ${failMsg}`);
          }
          
          return NextResponse.json({ status: 'ok' });
        }
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Submit a task with callback">
    Include `request_type: "callback"` and your `callback_url` in the request:

    <Tabs>
      <Tab title="cURL">
        ```bash theme={null}
        curl -X POST https://api.apixo.ai/api/v1/generateTask/nano-banana \
          -H "Authorization: Bearer YOUR_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{
            "request_type": "callback",
            "callback_url": "https://your-domain.com/webhook/apixo",
            "input": {
              "mode": "text-to-image",
              "prompt": "A beautiful landscape"
            }
          }'
        ```
      </Tab>

      <Tab title="JavaScript">
        ```javascript theme={null}
        const response = await fetch('https://api.apixo.ai/api/v1/generateTask/nano-banana', {
          method: 'POST',
          headers: {
            'Authorization': 'Bearer YOUR_API_KEY',
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            request_type: 'callback',
            callback_url: 'https://your-domain.com/webhook/apixo',
            input: {
              mode: 'text-to-image',
              prompt: 'A beautiful landscape',
            },
          }),
        });
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        response = requests.post(
            'https://api.apixo.ai/api/v1/generateTask/nano-banana',
            headers={
                'Authorization': 'Bearer YOUR_API_KEY',
                'Content-Type': 'application/json',
            },
            json={
                'request_type': 'callback',
                'callback_url': 'https://your-domain.com/webhook/apixo',
                'input': {
                    'mode': 'text-to-image',
                    'prompt': 'A beautiful landscape',
                },
            }
        )
        ```
      </Tab>
    </Tabs>
  </Step>
</Steps>

## Webhook Payload

### Success

```json theme={null}
{
  "code": 200,
  "message": "success",
  "data": {
    "taskId": "task_abc123xyz",
    "state": "success",
    "resultJson": "{\"resultUrls\":[\"https://cdn.apixo.ai/output/abc.jpg\"]}",
    "costTime": 12500,
    "createTime": 1704067200000,
    "completeTime": 1704067212500
  }
}
```

### Failure

```json theme={null}
{
  "code": 200,
  "message": "success",
  "data": {
    "taskId": "task_abc123xyz",
    "state": "failed",
    "failCode": "CONTENT_VIOLATION",
    "failMsg": "Content violates usage policy",
    "createTime": 1704067200000,
    "completeTime": 1704067205000
  }
}
```

### Payload Fields

<ResponseField name="taskId" type="string">
  Unique task identifier.
</ResponseField>

<ResponseField name="state" type="string">
  Final task state: `success` or `failed`.
</ResponseField>

<ResponseField name="resultJson" type="string">
  JSON string containing `resultUrls` array. Only present on success.
</ResponseField>

<ResponseField name="costTime" type="integer">
  Processing time in milliseconds.
</ResponseField>

<ResponseField name="failCode" type="string">
  Error code. Only present on failure.
</ResponseField>

<ResponseField name="failMsg" type="string">
  Human-readable error message. Only present on failure.
</ResponseField>

## Retry Policy

If your webhook endpoint fails to respond with HTTP 200:

| Attempt   | Delay      |
| --------- | ---------- |
| 1st retry | 30 seconds |
| 2nd retry | 2 minutes  |
| 3rd retry | 10 minutes |

After 3 failed attempts, the webhook is abandoned. You can still query task status via the [Status Task](/api-reference/status-task) endpoint.

## Security Recommendations

### Implement Idempotency

<Note>
  Webhooks may be delivered more than once. Use `taskId` to deduplicate and prevent processing the same result twice.
</Note>

```javascript theme={null}
const processedTasks = new Set();

app.post('/webhook/apixo', (req, res) => {
  const { taskId } = req.body.data;
  
  if (processedTasks.has(taskId)) {
    return res.status(200).send('Already processed');
  }
  
  processedTasks.add(taskId);
  // Process the task...
  
  res.status(200).send('OK');
});
```

### Use HTTPS Only

Always use HTTPS for your webhook endpoint to ensure data is encrypted in transit.

## Testing Webhooks

Use [ngrok](https://ngrok.com) to expose your local server during development:

```bash theme={null}
ngrok http 3000
# Returns: https://abc123.ngrok.io — use this as your callback_url
```

<Tip>
  You can still query task status via the API even when using callback mode, which is useful for debugging.
</Tip>
