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

# Callback Webhooks

> Receive Agent Loop results via webhooks

Callback webhooks allow Agent Loops to send results to external systems when execution completes. Use callbacks to integrate agent workflows with your applications, trigger subsequent actions, or deliver reports.

## Overview

A callback is an HTTP POST request sent to your specified endpoint when an Agent Loop finishes:

```
Agent Loop Executes → Completes Task → Sends POST to Your Webhook
```

Your webhook receives the agent's output and can process it however needed.

## Configuring Callbacks

<Steps>
  <Step title="Create/Edit Agent Loop">
    Open the agent loop you want to configure.
  </Step>

  <Step title="Find Callback Section">
    Scroll to the **Callback Webhook** configuration section.
  </Step>

  <Step title="Enter Webhook URL">
    Provide your webhook endpoint:

    ```
    https://your-domain.com/agent-results
    ```
  </Step>

  <Step title="Add Headers (Optional)">
    Include authentication or custom headers:

    ```
    Authorization: Bearer YOUR_TOKEN
    X-Custom-Header: value
    ```
  </Step>

  <Step title="Save">
    Click **Save** to enable callbacks.
  </Step>
</Steps>

## Callback Payload

Your webhook receives a POST request with this JSON structure:

```json theme={null}
{
  "agent_loop_id": "loop_abc123",
  "agent_loop_name": "Daily Sales Report",
  "execution_id": "exec_xyz789",
  "status": "completed",
  "result": {
    "output": "Sales Report for January 15, 2024\n\nTotal Revenue: $12,450...",
    "iterations_used": 5,
    "tool_calls": 3
  },
  "metadata": {
    "started_at": "2024-01-15T10:00:00Z",
    "completed_at": "2024-01-15T10:02:30Z",
    "duration_seconds": 150,
    "model": "gpt-4",
    "tokens_used": 1250
  },
  "error": null
}
```

### Payload Fields

| Field                    | Type        | Description                         |
| ------------------------ | ----------- | ----------------------------------- |
| `agent_loop_id`          | string      | Unique agent loop identifier        |
| `agent_loop_name`        | string      | Display name of the agent loop      |
| `execution_id`           | string      | Unique execution identifier         |
| `status`                 | string      | `completed`, `failed`, or `timeout` |
| `result.output`          | string      | The agent's final response          |
| `result.iterations_used` | number      | Reasoning steps taken               |
| `result.tool_calls`      | number      | Tools used during execution         |
| `metadata`               | object      | Execution timing and stats          |
| `error`                  | string/null | Error message if failed             |

### Status Values

**`completed`**: Agent successfully finished the task

```json theme={null}
{
  "status": "completed",
  "result": { "output": "..." },
  "error": null
}
```

**`failed`**: Agent encountered an error

```json theme={null}
{
  "status": "failed",
  "result": null,
  "error": "Tool call failed: HTTP 500 from API"
}
```

**`timeout`**: Execution exceeded time limit

```json theme={null}
{
  "status": "timeout",
  "result": { "output": "Partial result..." },
  "error": "Execution timeout after 300 seconds"
}
```

## Implementing a Callback Webhook

### Example: Node.js/Express

```javascript theme={null}
const express = require('express');
const app = express();
app.use(express.json());

app.post('/agent-results', (req, res) => {
  const {
    agent_loop_id,
    agent_loop_name,
    status,
    result,
    error
  } = req.body;

  console.log(`Agent Loop "${agent_loop_name}" ${status}`);

  if (status === 'completed') {
    // Process successful result
    const output = result.output;

    // Example: Send to Slack
    sendToSlack({
      text: `Agent Report:\n\n${output}`
    });

    // Example: Save to database
    database.saveReport({
      agent_loop_id,
      output,
      created_at: new Date()
    });

  } else if (status === 'failed') {
    // Handle error
    console.error(`Agent failed: ${error}`);

    // Send alert
    sendAlert({
      title: 'Agent Loop Failed',
      message: error
    });
  }

  // Acknowledge receipt
  res.json({ received: true });
});

app.listen(3000);
```

### Example: Python/Flask

```python theme={null}
from flask import Flask, request, jsonify
import requests

app = Flask(__name__)

@app.route('/agent-results', methods=['POST'])
def agent_callback():
    data = request.json

    agent_name = data['agent_loop_name']
    status = data['status']

    if status == 'completed':
        output = data['result']['output']

        # Send to Slack
        slack_webhook_url = 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL'
        requests.post(slack_webhook_url, json={
            'text': f'Agent Report from {agent_name}:\n\n{output}'
        })

        # Log to file
        with open('agent_reports.txt', 'a') as f:
            f.write(f'\n\n=== {agent_name} ===\n{output}\n')

    elif status == 'failed':
        error = data['error']
        print(f'Agent {agent_name} failed: {error}')

    return jsonify({'received': True})

if __name__ == '__main__':
    app.run(port=3000)
```

## Response Expected

Your webhook should return a JSON response with HTTP 200 status:

```json theme={null}
{
  "received": true
}
```

Optional fields:

```json theme={null}
{
  "received": true,
  "processed": true,
  "message": "Report saved successfully"
}
```

If your webhook returns an error status (4xx or 5xx), the callback will be retried (coming soon).

## Use Cases

### Send to Messaging Platforms

Forward agent results to Slack, Teams, or Discord:

```javascript theme={null}
async function sendToSlack(output) {
  await fetch(process.env.SLACK_WEBHOOK_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      text: output,
      username: 'Agent Bot',
      icon_emoji: ':robot_face:'
    })
  });
}
```

### Save to Database

Store results for historical tracking:

```javascript theme={null}
async function saveToDatabase(data) {
  await db.agentResults.insert({
    agent_loop_id: data.agent_loop_id,
    execution_id: data.execution_id,
    output: data.result.output,
    status: data.status,
    created_at: new Date(data.metadata.completed_at)
  });
}
```

### Trigger Workflows

Start additional workflows based on results:

```javascript theme={null}
if (result.output.includes('ERROR')) {
  // Trigger incident response
  await createIncident({
    title: 'Agent Loop Error Detected',
    description: result.output
  });
}
```

### Send Emails

Email reports to stakeholders:

```javascript theme={null}
if (status === 'completed') {
  await sendEmail({
    to: 'team@company.com',
    subject: `Daily Report from ${agent_loop_name}`,
    body: result.output
  });
}
```

## Security

<CardGroup cols={2}>
  <Card title="Use HTTPS" icon="lock">
    Always use HTTPS endpoints for callbacks
  </Card>

  <Card title="Authenticate Requests" icon="key">
    Verify requests come from Chatbot Platform
  </Card>

  <Card title="Validate Payload" icon="shield-check">
    Check payload structure before processing
  </Card>

  <Card title="Rate Limiting" icon="gauge">
    Implement rate limits on your endpoint
  </Card>
</CardGroup>

### Authenticating Callbacks

Add a secret token to callback headers:

**Agent Loop Configuration**:

```
Callback URL: https://your-domain.com/agent-results
Headers:
  X-Webhook-Secret: your-secret-token-here
```

**Your Webhook**:

```javascript theme={null}
app.post('/agent-results', (req, res) => {
  const secret = req.headers['x-webhook-secret'];

  if (secret !== process.env.WEBHOOK_SECRET) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  // Process callback...
});
```

### Request Signature (Coming Soon)

Future support for HMAC-SHA256 signature verification:

```
X-Signature: sha256=abc123...
```

Your webhook would verify the signature matches the payload.

## Testing Callbacks

### Manual Testing

Use a service like webhook.site:

<Steps>
  <Step title="Create Test Endpoint">
    Go to [webhook.site](https://webhook.site) and copy your unique URL.
  </Step>

  <Step title="Configure Agent Loop">
    Set the webhook.site URL as your callback URL.
  </Step>

  <Step title="Run Agent Loop">
    Execute the agent loop manually.
  </Step>

  <Step title="View Request">
    Check webhook.site to see the callback payload.
  </Step>
</Steps>

### Local Testing with Ngrok

Test with your local development server:

<Steps>
  <Step title="Start Ngrok">
    ```bash theme={null}
    ngrok http 3000
    ```
  </Step>

  <Step title="Use Ngrok URL">
    Configure agent loop with the ngrok URL:

    ```
    https://abc123.ngrok.io/agent-results
    ```
  </Step>

  <Step title="Run Agent">
    Execute and receive callback on your local server.
  </Step>
</Steps>

## Troubleshooting

### Callback Not Received

**Check**:

* Callback URL is correct and accessible
* Your endpoint is running
* Firewall allows incoming requests
* Agent loop completed successfully

### Callback Failed

**Check**:

* Endpoint returns 200 status
* Response is valid JSON
* No timeout on your endpoint
* Check execution logs for details

### Duplicate Callbacks

If you receive multiple callbacks:

* Check for retry logic (future feature)
* Verify webhook is not called multiple times in your code
* Use `execution_id` to deduplicate

## Best Practices

<Callout type="tip">
  Always respond with HTTP 200 status quickly. Process the callback data asynchronously if it takes time.
</Callout>

**Do**:

* Return 200 immediately
* Process data in background jobs
* Log all callbacks for debugging
* Validate payload structure
* Handle errors gracefully

**Don't**:

* Perform long operations synchronously
* Return errors for non-critical issues
* Skip authentication
* Assume payload structure won't change

### Async Processing Pattern

```javascript theme={null}
app.post('/agent-results', async (req, res) => {
  const data = req.body;

  // Immediately acknowledge
  res.json({ received: true });

  // Process asynchronously
  processCallback(data).catch(err => {
    console.error('Callback processing failed:', err);
  });
});

async function processCallback(data) {
  // Long-running operations here
  await saveToDatabase(data);
  await sendNotifications(data);
  await triggerWorkflows(data);
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Getting Started" icon="rocket" href="/agent-loops/getting-started">
    Create your first agent loop
  </Card>

  <Card title="MCP Servers" icon="server" href="/agent-loops/mcp-servers">
    Add tools to your agents
  </Card>
</CardGroup>
