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

# Webhooks

> Receive real-time notifications when automation tasks complete

## Overview

Webhooks provide a push-based mechanism for receiving automation task results in real-time. Instead of continuously polling for task status, Narada Cloud automatically sends the response to your specified endpoint when the task completes.

<CardGroup cols={2}>
  <Card title="Real-time Updates" icon="bolt">
    Receive results immediately when tasks complete, eliminating polling delays
  </Card>

  <Card title="Reduced Load" icon="gauge-low">
    Lower server load and API usage compared to continuous polling
  </Card>

  <Card title="Scalable" icon="chart-line">
    Handle high-volume automation workflows efficiently
  </Card>

  <Card title="Efficient" icon="gauge-high">
    Process automation results without continuous API polling
  </Card>
</CardGroup>

## How Webhooks Work

When you submit an automation task via the [Remote Dispatch API](/api-reference/remote-dispatch), you can provide a `callbackUrl`. Once the task completes, Narada Cloud sends an HTTP POST request to your endpoint with the complete task results.

<Steps>
  <Step title="Submit Task with Webhook">
    Include `callbackUrl` when creating a remote dispatch request
  </Step>

  <Step title="Task Executes">
    Narada Cloud processes your automation task asynchronously
  </Step>

  <Step title="Receive Callback">
    When complete, Narada Cloud POSTs the results to your callback URL
  </Step>

  <Step title="Process Results">
    Your endpoint receives and processes the webhook payload
  </Step>
</Steps>

<Info>
  Webhooks are recommended for production applications where real-time notifications and scalability are important.
</Info>

## Configuration

### Setting Up a Webhook Endpoint

Configure webhooks by providing the `callbackUrl` parameter when creating a remote dispatch request:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST 'https://api.narada.ai/fast/v2/remote-dispatch' \
    -H 'Content-Type: application/json' \
    -H 'x-api-key: YOUR_API_KEY' \
    -d '{
      "prompt": "/Operator search for software engineering jobs and count the results",
      "browserWindowId": "your-browser-window-id",
      "callbackUrl": "https://your-domain.com/api/narada/webhook",
      "callbackHeaders": {
        "Authorization": "Bearer YOUR_WEBHOOK_TOKEN"
      },
      "callbackSecret": "your-secret-key-here"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.narada.ai/fast/v2/remote-dispatch", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": process.env.NARADA_API_KEY,
    },
    body: JSON.stringify({
      prompt: "/Operator search for software engineering jobs and count the results",
      browserWindowId: "your-browser-window-id",
      callbackUrl: "https://your-domain.com/api/narada/webhook",
      callbackHeaders: {
        Authorization: `Bearer ${process.env.WEBHOOK_TOKEN}`,
      },
      callbackSecret: process.env.NARADA_CALLBACK_SECRET,
    }),
  });

  const data = await response.json();
  console.log(`Task submitted: ${data.requestId}`);
  ```

  ```python Python theme={null}
  import requests
  import os

  response = requests.post(
    'https://api.narada.ai/fast/v2/remote-dispatch',
    headers={
      'Content-Type': 'application/json',
      'x-api-key': os.getenv('NARADA_API_KEY')
    },
    json={
      'prompt': '/Operator search for software engineering jobs and count the results',
      'browserWindowId': 'your-browser-window-id',
      'callbackUrl': 'https://your-domain.com/api/narada/webhook',
      'callbackHeaders': {
        'Authorization': f'Bearer {os.getenv("WEBHOOK_TOKEN")}'
      },
      'callbackSecret': os.getenv('NARADA_CALLBACK_SECRET')
    }
  )

  data = response.json()
  print(f"Task submitted: {data['requestId']}")
  ```
</CodeGroup>

### Configuration Parameters

<ParamField body="callbackUrl" type="string">
  The HTTPS URL where Narada Cloud will send the webhook POST request. Must be a publicly accessible endpoint that accepts POST requests.
</ParamField>

<ParamField body="callbackHeaders" type="object">
  **Optional.** HTTP headers to include when sending the webhook POST request. These headers are sent with the webhook notification, allowing you to authenticate or customize the request.

  ```json theme={null}
  {
    "callbackUrl": "https://example.com/webhook",
    "callbackHeaders": {
      "Authorization": "Bearer <token>",
      "X-Custom-Header": "value"
    }
  }
  ```

  <Note>
    `callbackHeaders` is optional and only needed if your webhook endpoint requires authentication or custom headers. If your endpoint doesn't require any special headers, you can omit this parameter.
  </Note>

  <Tip>
    Use `callbackHeaders` to send authentication tokens, API keys, or custom headers with your webhook requests. This is particularly useful when your webhook endpoint requires authentication.
  </Tip>
</ParamField>

<ParamField body="callbackSecret" type="string">
  Optional secret key included in the webhook payload body for verification. Use this to validate that webhook requests are genuinely from Narada Cloud.
</ParamField>

<Warning>
  **HTTPS Required**: Your callback URL must use HTTPS to ensure your `callbackSecret`, `callbackHeaders`, and webhook data are encrypted in transit.
</Warning>

## How Callback Headers Work

When you provide `callbackHeaders` (or `callback_headers` in Python), Narada Cloud stores these headers and includes them in the HTTP POST request when sending the webhook notification. This allows you to:

* **Authenticate webhook requests** using Bearer tokens, API keys, or custom authentication headers
* **Customize webhook delivery** with custom headers required by your endpoint
* **Integrate with existing systems** that require specific header formats

## Webhook Payload

When an automation task completes, Narada Cloud sends a POST request to your `callbackUrl` with the following JSON payload:

### Payload Structure

<ResponseField name="requestId" type="string" required>
  The unique identifier for the automation task. Use this to correlate webhook notifications with your original requests.
</ResponseField>

<ResponseField name="secret" type="string | null">
  The `callbackSecret` you provided when creating the task. Returns `null` if no secret was specified.
</ResponseField>

<ResponseField name="status" type="string" required>
  Current status of the automation task. Possible values:

  * `"input-required"` - Task requires additional user input
  * `"success"` - Task completed successfully
  * `"error"` - Task failed with an error
</ResponseField>

<ResponseField name="browserWindowId" type="string | null">
  The browser window ID where the task was executed. Returns `null` if not applicable.
</ResponseField>

<ResponseField name="response" type="object" required>
  The automation task response data containing the results.

  <Expandable title="Response Object Properties">
    <ResponseField name="text" type="string">
      The main response text from the automation task.
    </ResponseField>

    <ResponseField name="structuredOutput" type="object">
      Structured data if you specified a `responseFormat` in your original request. See [Structured Output documentation](/documentation/structured-output) for details.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  ISO 8601 timestamp when the task was initially created.
</ResponseField>

<ResponseField name="completedAt" type="string" required>
  ISO 8601 timestamp when the task completed.
</ResponseField>

<ResponseField name="usage" type="object | null">
  Usage statistics for the automation task. Returns `null` if not available.

  <Expandable title="Usage Object Properties">
    <ResponseField name="actions" type="integer">
      Number of actions performed during the automation task.
    </ResponseField>

    <ResponseField name="credits" type="number">
      Credits consumed by the task.
    </ResponseField>
  </Expandable>
</ResponseField>

### Example Payloads

<Tabs>
  <Tab title="Success">
    ```json theme={null}
    {
      "requestId": "req_abc123def456",
      "secret": "your-secret-key-here",
      "status": "success",
      "browserWindowId": "window_xyz789",
      "response": {
        "text": "Found 1,234 software engineering job listings in the search results."
      },
      "createdAt": "2024-11-21T10:00:00.000000+00:00",
      "completedAt": "2024-11-21T10:00:45.000000+00:00",
      "usage": {
        "actions": 12,
        "credits": 5
      }
    }
    ```
  </Tab>

  <Tab title="Error">
    ```json theme={null}
    {
      "requestId": "req_abc123def456",
      "secret": "your-secret-key-here",
      "status": "error",
      "browserWindowId": "window_xyz789",
      "response": {
        "text": "Failed to complete the task due to network timeout."
      },
      "createdAt": "2024-11-21T10:00:00.000000+00:00",
      "completedAt": "2024-11-21T10:02:30.000000+00:00",
      "usage": {
        "actions": 8,
        "credits": 3
      }
    }
    ```
  </Tab>

  <Tab title="Input Required">
    ```json theme={null}
    {
      "requestId": "req_abc123def456",
      "secret": "your-secret-key-here",
      "status": "input-required",
      "browserWindowId": "window_xyz789",
      "response": {
        "text": "The task requires authentication. Please log in to continue."
      },
      "createdAt": "2024-11-21T10:00:00.000000+00:00",
      "completedAt": "2024-11-21T10:00:15.000000+00:00",
      "usage": {
        "actions": 3,
        "credits": 1
      }
    }
    ```
  </Tab>

  <Tab title="Structured Output">
    ```json theme={null}
    {
      "requestId": "req_abc123def456",
      "secret": "your-secret-key-here",
      "status": "success",
      "browserWindowId": "window_xyz789",
      "response": {
        "text": "Successfully extracted job information.",
        "structuredOutput": {
          "title": "Senior Software Engineer",
          "company": "TechCorp Inc.",
          "location": "San Francisco, CA",
          "salary": "$150,000 - $200,000",
          "remote": true
        }
      },
      "createdAt": "2024-11-21T10:00:00.000000+00:00",
      "completedAt": "2024-11-21T10:00:52.000000+00:00",
      "usage": {
        "actions": 15,
        "credits": 6
      }
    }
    ```
  </Tab>
</Tabs>

## Implementing a Webhook Endpoint

### Basic Implementation

Create an endpoint that receives and processes webhook notifications:

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

    app.use(express.json());

    app.post('/api/narada/webhook', (req, res) => {
      const payload = req.body;

      // Verify authentication - check Authorization header from callbackHeaders
      const authHeader = req.headers.authorization;
      if (authHeader && authHeader !== `Bearer ${process.env.WEBHOOK_TOKEN}`) {
        return res.status(401).json({ error: 'Invalid authorization header' });
      }

      // Also verify the secret if provided
      if (payload.secret && payload.secret !== process.env.NARADA_CALLBACK_SECRET) {
        return res.status(401).json({ error: 'Invalid secret' });
      }

      // Process the webhook payload
      console.log(`Task ${payload.requestId} completed with status: ${payload.status}`);

      if (payload.status === 'success') {
        console.log('Response:', payload.response.text);
        // Handle successful completion
        handleSuccess(payload);
      } else if (payload.status === 'error') {
        console.error('Task failed:', payload.response.text);
        // Handle error
        handleError(payload);
      } else if (payload.status === 'input-required') {
        console.log('Input required:', payload.response.text);
        // Handle input requirement
        handleInputRequired(payload);
      }

      // Always respond with 200 to acknowledge receipt
      res.status(200).json({ received: true });
    });

    function handleSuccess(payload) {
      // Your success handling logic
      // Store results, trigger next steps, etc.
    }

    function handleError(payload) {
      // Your error handling logic
      // Log errors, retry tasks, send alerts, etc.
    }

    function handleInputRequired(payload) {
      // Your input handling logic
      // Notify users, pause workflows, etc.
    }

    app.listen(3000, () => {
      console.log('Webhook endpoint listening on port 3000');
    });
    ```
  </Tab>

  <Tab title="Python (FastAPI)">
    ```python theme={null}
    from fastapi import FastAPI, Request, HTTPException
    from pydantic import BaseModel
    from typing import Any, Literal, Mapping
    import os

    app = FastAPI()

    class RemoteDispatchUsage(BaseModel):
        actions: int
        credits: int

    class WebhookPayload(BaseModel):
        requestId: str
        secret: str | None
        status: Literal["input-required", "success", "error"]
        browserWindowId: str | None
        response: Mapping[str, Any]
        createdAt: str
        completedAt: str
        usage: RemoteDispatchUsage | None

    @app.post("/api/narada/webhook")
    async def handle_webhook(request: Request, payload: WebhookPayload):
        # Verify authentication - check Authorization header from callback_headers
        auth_header = request.headers.get("authorization")
        expected_token = os.getenv('WEBHOOK_TOKEN')
        if auth_header and expected_token:
            if auth_header != f"Bearer {expected_token}":
                raise HTTPException(status_code=401, detail="Invalid authorization header")

        # Also verify the secret if provided
        expected_secret = os.getenv('NARADA_CALLBACK_SECRET')
        if expected_secret and payload.secret != expected_secret:
            raise HTTPException(status_code=401, detail="Invalid secret")

        # Process the webhook payload
        print(f"Task {payload.requestId} completed with status: {payload.status}")

        if payload.status == "success":
            print(f"Response: {payload.response.get('text')}")
            handle_success(payload)
        elif payload.status == "error":
            print(f"Task failed: {payload.response.get('text')}")
            handle_error(payload)
        elif payload.status == "input-required":
            print(f"Input required: {payload.response.get('text')}")
            handle_input_required(payload)

        # Always respond with 200 to acknowledge receipt
        return {"received": True}

    def handle_success(payload: WebhookPayload):
        # Your success handling logic
        pass

    def handle_error(payload: WebhookPayload):
        # Your error handling logic
        pass

    def handle_input_required(payload: WebhookPayload):
        # Your input handling logic
        pass
    ```
  </Tab>
</Tabs>

### Response Requirements

Your webhook endpoint must:

<Steps>
  <Step title="Accept POST Requests">
    Configure your endpoint to accept HTTP POST requests with JSON payloads.
  </Step>

  <Step title="Respond Quickly">
    Return a `200 OK` response promptly to acknowledge receipt. Perform long-running operations asynchronously.
  </Step>
</Steps>

## Security Best Practices

### 1. Use HTTPS

Always use HTTPS endpoints to ensure your `callbackSecret` and webhook data are encrypted in transit.

```bash theme={null}
# ✅ Good
https://api.yourdomain.com/webhook

# ❌ Bad
http://api.yourdomain.com/webhook
```

### 2. Verify Authentication

You can authenticate webhook requests using either `callbackHeaders` or `callbackSecret`:

**Using callbackHeaders (Recommended for API-based auth):**

```javascript theme={null}
// Verify Authorization header from callbackHeaders
const authHeader = req.headers.authorization;
if (authHeader !== `Bearer ${process.env.WEBHOOK_TOKEN}`) {
  return res.status(401).json({ error: 'Unauthorized' });
}
```

**Using callbackSecret:**

```javascript theme={null}
// Verify secret from payload body
if (payload.secret !== process.env.NARADA_CALLBACK_SECRET) {
  return res.status(401).json({ error: 'Unauthorized' });
}
```

**Using both (Maximum security):**

```javascript theme={null}
// Verify both header and secret
const authHeader = req.headers.authorization;
const isValidHeader = authHeader === `Bearer ${process.env.WEBHOOK_TOKEN}`;
const isValidSecret = payload.secret === process.env.NARADA_CALLBACK_SECRET;

if (!isValidHeader || !isValidSecret) {
  return res.status(401).json({ error: 'Unauthorized' });
}
```

<Tip>
  Store your authentication tokens and secrets in environment variables, never in your code or version control. Use `callbackHeaders` for API-based authentication and `callbackSecret` for payload-based verification.
</Tip>

## Common Issues

<AccordionGroup>
  <Accordion title="Webhook Not Received">
    **Possible causes:**

    * Callback URL is not publicly accessible
    * Firewall blocking incoming requests
    * HTTPS certificate issues
    * Server is down or not responding

    **Solutions:**

    * Verify your endpoint is publicly accessible
    * Check firewall and security group settings
    * Ensure valid SSL certificate
    * Monitor server uptime and health
  </Accordion>
</AccordionGroup>

## Comparison: Webhooks vs Polling

| Feature               | Webhooks            | Polling                  |
| --------------------- | ------------------- | ------------------------ |
| **Response Time**     | Immediate           | Delayed by poll interval |
| **Server Load**       | Low                 | Higher (active requests) |
| **Implementation**    | Moderate complexity | Simple                   |
| **Firewall Friendly** | Requires open ports | Yes                      |
| **Scalability**       | Excellent           | Limited                  |
| **Real-time Updates** | Yes                 | No                       |
| **API Efficiency**    | High                | Lower                    |
| **Production Ready**  | Yes (recommended)   | Yes                      |

<Tip>
  Use webhooks for production applications where real-time notifications are important. Use [polling via Get Task Status](/api-reference/get-task-status) when webhook setup is not feasible.
</Tip>

## Best Practices Summary

<CardGroup cols={2}>
  <Card title="Respond Quickly" icon="bolt">
    Return `200 OK` promptly and process long-running tasks asynchronously
  </Card>

  <Card title="Validate Authentication" icon="shield-check">
    Verify webhooks using callbackHeaders or callbackSecret to ensure requests are from Narada Cloud
  </Card>

  <Card title="Monitor & Log" icon="chart-line">
    Maintain comprehensive logs for debugging and monitoring webhook delivery
  </Card>

  <Card title="Secure Your Endpoint" icon="lock">
    Use HTTPS to protect your callbackHeaders, callbackSecret, and webhook data in transit
  </Card>
</CardGroup>
