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

# Webhooks

> Receive real-time notifications when action workflows reach checkpoints or complete.

Webhooks let you receive real-time notifications when action workflows reach checkpoints or complete. They are required for the [execute workflow](/api-reference/actions#execute-action) used by `new_content` actions.

## Register webhook

```
POST /webhooks
```

Register a new webhook endpoint to receive event notifications.

### Request body

| Field    | Type      | Required | Description                                         |
| -------- | --------- | -------- | --------------------------------------------------- |
| `url`    | string    | Yes      | HTTPS endpoint URL that will receive webhook events |
| `events` | string\[] | Yes      | Array of event types to subscribe to                |

### Valid events

| Event               | Description                                                                 |
| ------------------- | --------------------------------------------------------------------------- |
| `action.checkpoint` | Workflow paused at an approval checkpoint (outline, draft, or final review) |
| `action.completed`  | Workflow finished successfully                                              |
| `action.failed`     | Workflow encountered an error                                               |

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST "https://topify-customer-api-production.up.railway.app/api/public/v1/webhooks" \
      -H "X-API-Key: tk_live_..." \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://example.com/webhooks/topify",
        "events": ["action.checkpoint", "action.completed", "action.failed"]
      }'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    resp = client.post("/webhooks", json={
        "url": "https://example.com/webhooks/topify",
        "events": ["action.checkpoint", "action.completed", "action.failed"],
    })
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const resp = await fetch(`${BASE}/webhooks`, {
      method: "POST",
      headers: { ...headers, "Content-Type": "application/json" },
      body: JSON.stringify({
        url: "https://example.com/webhooks/topify",
        events: ["action.checkpoint", "action.completed", "action.failed"],
      }),
    });
    ```
  </Tab>
</Tabs>

### Response (201 Created)

```json theme={null}
{
  "success": true,
  "data": {
    "id": "wh_a1b2c3d4-...",
    "url": "https://example.com/webhooks/topify",
    "events": ["action.checkpoint", "action.completed", "action.failed"],
    "is_active": true,
    "secret": "whsec_k7m9p2...",
    "created_at": "2026-04-08T12:00:00Z"
  }
}
```

<Warning>
  The `secret` field is only returned once at creation time. Store it securely -- you will need it to verify webhook signatures.
</Warning>

### Error responses

| Status | Cause                                                                     |
| ------ | ------------------------------------------------------------------------- |
| `400`  | Missing `url`, non-HTTPS URL, empty `events` array, or invalid event type |
| `403`  | API key has read-only access                                              |
| `409`  | A webhook with this URL already exists                                    |

***

## List webhooks

```
GET /webhooks
```

Returns all registered webhooks for your account.

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl "https://topify-customer-api-production.up.railway.app/api/public/v1/webhooks" \
      -H "X-API-Key: tk_live_..."
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    resp = client.get("/webhooks")
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const resp = await fetch(`${BASE}/webhooks`, { headers });
    ```
  </Tab>
</Tabs>

### Response

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": "wh_a1b2c3d4-...",
      "url": "https://example.com/webhooks/topify",
      "events": ["action.checkpoint", "action.completed", "action.failed"],
      "is_active": true,
      "created_at": "2026-04-08T12:00:00Z"
    }
  ]
}
```

<Note>
  The `secret` field is not included in list responses. It is only returned when the webhook is first created.
</Note>

***

## Delete webhook

```
DELETE /webhooks/{webhook_id}
```

Permanently remove a webhook. Events will no longer be delivered to the endpoint.

### Path parameters

| Param        | Type   | Description    |
| ------------ | ------ | -------------- |
| `webhook_id` | string | The webhook ID |

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X DELETE "https://topify-customer-api-production.up.railway.app/api/public/v1/webhooks/{webhook_id}" \
      -H "X-API-Key: tk_live_..."
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    resp = client.delete(f"/webhooks/{webhook_id}")
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const resp = await fetch(`${BASE}/webhooks/${webhookId}`, {
      method: "DELETE",
      headers,
    });
    ```
  </Tab>
</Tabs>

### Response

```json theme={null}
{
  "success": true,
  "data": {
    "id": "wh_a1b2c3d4-...",
    "message": "Webhook deleted successfully"
  }
}
```

### Error responses

| Status | Cause                        |
| ------ | ---------------------------- |
| `403`  | API key has read-only access |
| `404`  | Webhook not found            |

***

## Webhook payload format

When an event fires, Topify sends a POST request to your registered URL with the following JSON body.

### `action.checkpoint` payload

```json theme={null}
{
  "event": "action.checkpoint",
  "timestamp": "2026-04-08T21:19:13.463Z",
  "data": {
    "action_id": "a1b2c3d4-...",
    "workflow_id": "w1x2y3z4-...",
    "checkpoint": "outline",
    "content": "## Article Outline\n\n1. Introduction to project management tools...",
    "options": [
      { "action": "approve", "label": "Approve & Write" },
      { "action": "edit", "label": "Edit Outline" },
      { "action": "regenerate", "label": "Regenerate" },
      { "action": "reject", "label": "Cancel" }
    ]
  }
}
```

### `action.completed` payload

```json theme={null}
{
  "event": "action.completed",
  "timestamp": "2026-04-08T21:25:00.000Z",
  "data": {
    "action_id": "a1b2c3d4-...",
    "workflow_id": "w1x2y3z4-...",
    "result": {
      "title": "Best Project Management Tools in 2026",
      "content": "..."
    }
  }
}
```

### `action.failed` payload

```json theme={null}
{
  "event": "action.failed",
  "timestamp": "2026-04-08T21:22:00.000Z",
  "data": {
    "action_id": "a1b2c3d4-...",
    "workflow_id": "w1x2y3z4-...",
    "error": "LLM generation timed out after 120 seconds"
  }
}
```

### Payload fields

| Field              | Type          | Description                                                           |
| ------------------ | ------------- | --------------------------------------------------------------------- |
| `event`            | string        | Event type (`action.checkpoint`, `action.completed`, `action.failed`) |
| `timestamp`        | string        | ISO 8601 timestamp of when the event occurred                         |
| `data.action_id`   | string (UUID) | The action that triggered the event                                   |
| `data.workflow_id` | string (UUID) | The active workflow ID                                                |
| `data.checkpoint`  | string        | Checkpoint name (only for `action.checkpoint`)                        |
| `data.content`     | string        | Content to review (only for `action.checkpoint`)                      |
| `data.options`     | object\[]     | Available response actions (only for `action.checkpoint`)             |
| `data.result`      | object        | Final output (only for `action.completed`)                            |
| `data.error`       | string        | Error message (only for `action.failed`)                              |

***

## Verifying signatures

Every webhook request includes an `X-Webhook-Signature` header containing an HMAC-SHA256 signature of the raw request body, computed using the webhook secret returned at creation time.

Always verify this signature before processing the payload to ensure requests are authentic.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import hmac
    import hashlib

    def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
        expected = hmac.new(
            secret.encode(),
            body,
            hashlib.sha256,
        ).hexdigest()
        return hmac.compare_digest(expected, signature)

    # In your webhook handler:
    signature = request.headers["X-Webhook-Signature"]
    is_valid = verify_webhook(request.body, signature, WEBHOOK_SECRET)
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    import { createHmac, timingSafeEqual } from "crypto";

    function verifyWebhook(body, signature, secret) {
      const expected = createHmac("sha256", secret)
        .update(body)
        .digest("hex");
      return timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(signature),
      );
    }

    // In your webhook handler:
    const signature = req.headers["x-webhook-signature"];
    const isValid = verifyWebhook(req.rawBody, signature, WEBHOOK_SECRET);
    ```
  </Tab>
</Tabs>

<Note>
  Your endpoint should return a `200` status code within 10 seconds. If your endpoint fails to respond or returns a non-2xx status, Topify will retry up to 3 times with exponential backoff.
</Note>
