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

# Webhooks

> Set up webhooks for real-time execution notifications

Webhooks let you receive real-time execution notifications from Asteroid over HTTP.

<Note>
  Only **Webhook V2** is supported. Webhook V1 is deprecated and no longer accepted by the API.
</Note>

## Overview

<CardGroup cols={2}>
  <Card title="Real-time Notifications" icon="bell">
    Get updates as execution events happen
  </Card>

  <Card title="Secure Delivery" icon="shield-check">
    Every request includes an RSA signature in `X-Asteroid-Signature`
  </Card>

  <Card title="Event Coverage" icon="plug">
    Subscribe to status, step, action, file, message, workflow, and batch events
  </Card>

  <Card title="Filterable Notifications" icon="filter">
    Filter by event type, payload fields, and execution metadata
  </Card>
</CardGroup>

## Configuration

### Setting Up Your Webhook

<Steps>
  <Step title="Create a Webhook Integration">
    In Asteroid, go to **Integrations** → **Add Integration** → **Webhook**, then set:

    * **URL**: The endpoint that will receive execution events
    * **Headers**: Optional custom headers Asteroid should include

    <Note>
      Webhook requests are always `POST` with a JSON body in the V2 format documented below.
    </Note>
  </Step>

  <Step title="Attach the Integration to an Agent">
    Attach the webhook integration to each agent you want notifications from. You can configure:

    * **Event rules**: choose which event types should trigger notifications
    * **Field filters**: filter by payload fields for specific event types
    * **Metadata filter**: only trigger when execution metadata key/value pairs match
    * **Unwrap result** (optional): for `ExecutionCompleted`, send only `payload.result` instead of the full envelope

    See [Filtering & Rules](/fundamentals/integrations/overview#filtering--rules) for rule semantics.
  </Step>

  <Step title="Verify Signatures in Your Endpoint">
    Validate the `X-Asteroid-Signature` header against the raw request body before processing.
  </Step>
</Steps>

## V2 Payload Structure

Each webhook request body is a JSON object with this top-level shape:

```json theme={null}
{
  "type": "execution",
  "event_id": "83b7b49f-1f73-4f86-9d89-6fd8dd2557f3",
  "timestamp": "2024-01-15T10:30:00Z",
  "info": {
    "event": "EXECUTION_COMPLETED",
    "execution_id": "a2c7f3b3-cbc8-4b6a-a5f2-c5f6cb548e95",
    "execution_url": "https://platform.asteroid.ai/executions/a2c7f3b3-cbc8-4b6a-a5f2-c5f6cb548e95",
    "agent_id": "6bf63aaf-6e3e-4115-8f13-e707d627582f",
    "agent_name": "Data Extraction Agent",
    "metadata": {
      "environment": "production"
    },
    "payload": {
      "result": { /* execution result data */ },
      "reasoning": "Task completed successfully",
      "outcome": "success"
    }
  }
}
```

### Top-Level Fields

<ParamField path="type" type="string" required>
  The category of the notification. Currently only `"execution"` is supported.
</ParamField>

<ParamField path="event_id" type="string" required>
  Unique identifier generated for this specific webhook notification payload.
</ParamField>

<ParamField path="timestamp" type="string" required>
  ISO 8601 timestamp when the event occurred.
</ParamField>

<ParamField path="info" type="object" required>
  Execution event context and event-specific payload. See [Execution Info](#execution-info-structure).
</ParamField>

### Execution Info Structure

`info` contains execution context plus an event-specific `payload`.

<ParamField path="info.event" type="string" required>
  Event name. See [Execution Events](#execution-events) for supported values.
</ParamField>

<ParamField path="info.execution_id" type="string" required>
  Execution UUID.
</ParamField>

<ParamField path="info.execution_url" type="string" required>
  URL to view this execution (or batch for batch events) in Asteroid.
</ParamField>

<ParamField path="info.agent_id" type="string" required>
  Agent UUID.
</ParamField>

<ParamField path="info.agent_name" type="string" required>
  Agent display name.
</ParamField>

<ParamField path="info.metadata" type="object">
  Optional execution metadata map. Included when metadata was supplied at execution creation time.
</ParamField>

<ParamField path="info.payload" type="object" required>
  Event-specific data. The schema depends on `info.event`.
</ParamField>

## Execution Events

Asteroid currently emits the following webhook events:

### Core execution status

* `EXECUTION_STARTED` -> payload: `{}`
* `EXECUTION_COMPLETED` -> payload: `{ result: object, reasoning: string, outcome: string }`
* `EXECUTION_FAILED` -> payload: `{ reason: string }`
* `EXECUTION_CANCELLED` -> payload: `{ reason: string, cancelled_by: string }`
* `EXECUTION_PAUSED` -> payload: `{ reason: string, paused_by: string }`
* `EXECUTION_RESUMED` -> payload: `{ reason: string }`
* `EXECUTION_AWAITING_CONFIRMATION` -> payload: `{ reason: string }`

### Action and step events

* `EXECUTION_ACTION_STARTED` -> `{ action_id, action_name, arguments, step_number }`
* `EXECUTION_ACTION_COMPLETED` -> `{ action_id, action_name, output, step_number, duration? }`
* `EXECUTION_ACTION_FAILED` -> `{ action_id, action_name, failure, step_number, duration?, os_error? }`
* `EXECUTION_STEP_STARTED` -> `{ step }`
* `EXECUTION_STEP_PROCESSED` -> `{ step }`

### Execution detail events

* `EXECUTION_MESSAGE_ADDED` -> `{ message }`
* `EXECUTION_REASONING_ADDED` -> `{ reasoning }`
* `EXECUTION_FILE_ADDED` -> `{ file_id, file_name, mime_type, file_size, source, presigned_url }`
* `EXECUTION_PLAYWRIGHT_SCRIPT_GENERATED` -> `{ node_id, node_name, script, context, generated_at }`
* `EXECUTION_TRANSITIONED` -> `{ to_node, from_node_duration?, transition_type? }`
* `EXECUTION_WORKFLOW_UPDATED` -> `{ workflow_update: [...] }`
* `USER_MESSAGE_RECEIVED` -> `{ user_id, message, execution_was, injected_into }`

### Batch and test events

* `BATCH_STARTED` -> `{ batch_id, batch_name, item_count }`
* `BATCH_COMPLETED` -> `{ batch_id, batch_name, triggered_count, cancelled_count }`
* `EXECUTION_TEST` -> `{ message }` (used when testing integration connectivity)

<Note>
  For batch events, `info.execution_url` points to the agent batch page (`/agents/{agent_id}/batch`) rather than an execution page.
</Note>

### Example: `EXECUTION_ACTION_FAILED`

```json theme={null}
{
  "type": "execution",
  "event_id": "f0df4bd4-fdf8-445b-b870-1cd6ed329f39",
  "timestamp": "2026-03-27T16:10:05Z",
  "info": {
    "event": "EXECUTION_ACTION_FAILED",
    "execution_id": "a2c7f3b3-cbc8-4b6a-a5f2-c5f6cb548e95",
    "execution_url": "https://platform.asteroid.ai/executions/a2c7f3b3-cbc8-4b6a-a5f2-c5f6cb548e95",
    "agent_id": "6bf63aaf-6e3e-4115-8f13-e707d627582f",
    "agent_name": "Data Extraction Agent",
    "payload": {
      "action_id": "playwright_script-node-123",
      "action_name": "playwright_script",
      "failure": "Timeout waiting for selector",
      "step_number": 7,
      "duration": 1432,
      "os_error": {
        "message": "Target page, context or browser has been closed"
      }
    }
  }
}
```

## Security & Signature Verification

Asteroid signs every webhook body using:

* SHA-256 digest of the raw body bytes
* RSA PKCS#1 v1.5 signing
* Base64-encoded signature in the header

### Signature Header

```
X-Asteroid-Signature: <base64-signature>
```

<Warning>
  Verify signatures using the **raw** request body bytes before JSON parsing.
</Warning>

### Public Key

Use the webhook verification public key provided for your Asteroid environment/workspace.

### JavaScript (Node.js) verification example

```javascript theme={null}
import crypto from "node:crypto";

export function verifyWebhookSignature(rawBodyBuffer, signatureBase64, publicKeyPem) {
  const verifier = crypto.createVerify("sha256");
  verifier.update(rawBodyBuffer);
  verifier.end();

  return verifier.verify(publicKeyPem, signatureBase64, "base64");
}

// Example Express handler
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.header("X-Asteroid-Signature");
  if (!signature) {
    return res.status(401).json({ error: "Missing signature" });
  }

  const isValid = verifyWebhookSignature(req.body, signature, process.env.ASTEROID_WEBHOOK_PUBLIC_KEY_PEM);

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const event = JSON.parse(req.body.toString("utf8"));
  // process event...
  res.status(200).json({ received: true });
});
```

## Best Practices

<AccordionGroup>
  <Accordion title="Respond quickly with 2xx">
    Return a `2xx` response as quickly as possible, then process asynchronously in your own job queue.
  </Accordion>

  <Accordion title="Design for at-least-once delivery">
    Duplicate deliveries can happen. Use your own idempotency key strategy based on stable business identifiers.

    <Note>
      `event_id` is generated per notification payload and can change across retries/redeliveries, so do not rely on it as the only dedupe key.
    </Note>
  </Accordion>

  <Accordion title="Verify signatures before processing">
    Validate `X-Asteroid-Signature` against the raw body and reject invalid requests with `401`.
  </Accordion>

  <Accordion title="Handle retries safely">
    Asteroid performs up to 3 send attempts with short backoff in the notification worker before marking a delivery failed.
  </Accordion>

  <Accordion title="Log execution context">
    Log at least `info.event`, `info.execution_id`, `info.agent_id`, and your own processing result for observability.
  </Accordion>
</AccordionGroup>

## Testing Your Webhooks

<Tip>
  Use [webhook.site](https://webhook.site) for quick smoke tests, then validate with your real endpoint and signature verification enabled.
</Tip>

### Test Integration Event Example

Using "Test integration" in Asteroid sends an `EXECUTION_TEST` payload:

```json theme={null}
{
  "type": "execution",
  "event_id": "2ec89fb9-c2b5-4f6a-9fe9-c8f4f9dd6f66",
  "timestamp": "2024-01-15T10:30:00Z",
  "info": {
    "event": "EXECUTION_TEST",
    "execution_id": "00000000-0000-0000-0000-000000000003",
    "execution_url": "",
    "agent_id": "00000000-0000-0000-0000-000000000001",
    "agent_name": "Test Agent",
    "payload": {
      "message": "This is a test notification from Asteroid. If you're seeing this, your integration is working correctly! 🎉"
    }
  }
}
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Webhook Not Receiving Events">
    * Verify your endpoint is reachable from Asteroid and accepts `POST`
    * Ensure your notification rule configuration matches the events you expect
    * Confirm metadata filter values exactly match execution metadata values
  </Accordion>

  <Accordion title="Signature Verification Failing">
    * Verify against raw request bytes, not parsed JSON
    * Ensure you use RSA PKCS#1 v1.5 with SHA-256
    * Confirm you're using the correct environment public key
  </Accordion>

  <Accordion title="Receiving Duplicate Events">
    * Handle at-least-once semantics in your endpoint logic
    * Use stable execution fields for idempotency (not only `event_id`)
    * Return `2xx` once a duplicate is safely recognized
  </Accordion>
</AccordionGroup>
