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

# Chevre REST API Endpoints — Webhooks and Event Delivery

> Register webhook endpoints, manage event subscriptions, verify signatures, and handle Chevre real-time event deliveries via the REST API.

Webhooks let you subscribe to events that happen in Chevre and receive real-time HTTP POST notifications to a URL you control. When an event fires, Chevre sends a signed JSON payload to each registered webhook URL that subscribes to that event. Use the endpoints below to register, update, and remove webhooks. All management requests require your API key in the `Authorization` header as `Bearer <your_api_key>`.

***

## List webhooks

**`GET`** **`https://api.chevre.io/v1/webhooks`**

Returns all webhooks registered for the authenticated workspace.

### Example request

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url "https://api.chevre.io/v1/webhooks" \
    --header "Authorization: Bearer <your_api_key>" \
    --header "Accept: application/json"
  ```

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

  response = requests.get(
      "https://api.chevre.io/v1/webhooks",
      headers={"Authorization": "Bearer <your_api_key>"},
  )
  print(response.json())
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.chevre.io/v1/webhooks", {
    headers: {
      Authorization: "Bearer <your_api_key>",
      Accept: "application/json",
    },
  });
  const data = await response.json();
  console.log(data);
  ```
</CodeGroup>

### Example response

```json 200 theme={null}
{
  "data": [
    {
      "id": "wh_01RST",
      "url": "https://example.com/chevre-webhook",
      "events": [
        "automation.run.completed",
        "automation.run.failed",
        "member.invited"
      ],
      "enabled": true,
      "created_at": "2024-04-10T08:00:00Z",
      "updated_at": "2024-05-15T12:00:00Z"
    }
  ]
}
```

### Response fields

<ResponseField name="data" type="array">
  An array of webhook objects registered to the workspace.

  <Expandable title="webhook object">
    <ResponseField name="id" type="string">
      The unique identifier for the webhook, prefixed with `wh_`.
    </ResponseField>

    <ResponseField name="url" type="string">
      The destination URL that Chevre POSTs event payloads to.
    </ResponseField>

    <ResponseField name="events" type="array">
      The list of event types this webhook is subscribed to.
    </ResponseField>

    <ResponseField name="enabled" type="boolean">
      Whether the webhook is currently active. Disabled webhooks do not receive event deliveries.
    </ResponseField>

    <ResponseField name="created_at" type="string">
      ISO 8601 timestamp of when the webhook was registered.
    </ResponseField>

    <ResponseField name="updated_at" type="string">
      ISO 8601 timestamp of the most recent update.
    </ResponseField>
  </Expandable>
</ResponseField>

***

## Create a webhook

**`POST`** **`https://api.chevre.io/v1/webhooks`**

Registers a new webhook endpoint. Chevre immediately sends a test ping to the URL to verify reachability — the endpoint must respond with a `2xx` status code within 5 seconds or the registration will fail.

### Request body

<ParamField body="url" type="string" required>
  The HTTPS URL that Chevre will send event payloads to. Must use `https://`. Plain HTTP URLs are rejected.
</ParamField>

<ParamField body="events" type="array" required>
  An array of event type strings the webhook should subscribe to. At least one event is required. See [Available events](#available-webhook-events) for the full list.
</ParamField>

<ParamField body="secret" type="string" required>
  A signing secret used to compute the `Chevre-Signature` HMAC-SHA256 header on each delivery. Store this value securely — you cannot retrieve it after creation.
</ParamField>

<ParamField body="enabled" type="boolean" default="true">
  Whether the webhook should be active immediately after creation.
</ParamField>

### Example request

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url "https://api.chevre.io/v1/webhooks" \
    --header "Authorization: Bearer <your_api_key>" \
    --header "Content-Type: application/json" \
    --data '{
      "url": "https://example.com/chevre-webhook",
      "events": [
        "automation.run.completed",
        "automation.run.failed",
        "member.invited"
      ],
      "secret": "your-signing-secret",
      "enabled": true
    }'
  ```

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

  response = requests.post(
      "https://api.chevre.io/v1/webhooks",
      json={
          "url": "https://example.com/chevre-webhook",
          "events": [
              "automation.run.completed",
              "automation.run.failed",
              "member.invited",
          ],
          "secret": "your-signing-secret",
          "enabled": True,
      },
      headers={"Authorization": "Bearer <your_api_key>"},
  )
  print(response.json())
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.chevre.io/v1/webhooks", {
    method: "POST",
    headers: {
      Authorization: "Bearer <your_api_key>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      url: "https://example.com/chevre-webhook",
      events: [
        "automation.run.completed",
        "automation.run.failed",
        "member.invited",
      ],
      secret: "your-signing-secret",
      enabled: true,
    }),
  });
  const data = await response.json();
  console.log(data);
  ```
</CodeGroup>

### Example response

```json 201 theme={null}
{
  "data": {
    "id": "wh_01RST",
    "url": "https://example.com/chevre-webhook",
    "events": [
      "automation.run.completed",
      "automation.run.failed",
      "member.invited"
    ],
    "enabled": true,
    "created_at": "2024-06-01T14:22:00Z",
    "updated_at": "2024-06-01T14:22:00Z"
  }
}
```

<Note>
  The `secret` field is write-only and is **not** returned in any response after creation. Store it securely at registration time.
</Note>

### Response fields

<ResponseField name="data" type="object">
  The newly registered webhook object.

  <Expandable title="webhook object">
    <ResponseField name="id" type="string">
      The unique identifier for the webhook, prefixed with `wh_`.
    </ResponseField>

    <ResponseField name="url" type="string">
      The destination URL for event delivery.
    </ResponseField>

    <ResponseField name="events" type="array">
      The list of event types this webhook is subscribed to.
    </ResponseField>

    <ResponseField name="enabled" type="boolean">
      Whether the webhook is currently active.
    </ResponseField>

    <ResponseField name="created_at" type="string">
      ISO 8601 timestamp of when the webhook was registered.
    </ResponseField>

    <ResponseField name="updated_at" type="string">
      ISO 8601 timestamp of the most recent update.
    </ResponseField>
  </Expandable>
</ResponseField>

***

## Get a webhook

**`GET`** **`https://api.chevre.io/v1/webhooks/{id}`**

Retrieves a single webhook by its ID.

### Path parameters

<ParamField path="id" type="string" required>
  The unique identifier of the webhook to retrieve (e.g. `wh_01RST`).
</ParamField>

### Example request

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url "https://api.chevre.io/v1/webhooks/wh_01RST" \
    --header "Authorization: Bearer <your_api_key>" \
    --header "Accept: application/json"
  ```

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

  response = requests.get(
      "https://api.chevre.io/v1/webhooks/wh_01RST",
      headers={"Authorization": "Bearer <your_api_key>"},
  )
  print(response.json())
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.chevre.io/v1/webhooks/wh_01RST", {
    headers: {
      Authorization: "Bearer <your_api_key>",
      Accept: "application/json",
    },
  });
  const data = await response.json();
  console.log(data);
  ```
</CodeGroup>

### Example response

```json 200 theme={null}
{
  "data": {
    "id": "wh_01RST",
    "url": "https://example.com/chevre-webhook",
    "events": [
      "automation.run.completed",
      "automation.run.failed",
      "member.invited"
    ],
    "enabled": true,
    "created_at": "2024-04-10T08:00:00Z",
    "updated_at": "2024-05-15T12:00:00Z"
  }
}
```

### Response fields

<ResponseField name="data" type="object">
  The requested webhook object.

  <Expandable title="webhook object">
    <ResponseField name="id" type="string">
      The unique identifier for the webhook.
    </ResponseField>

    <ResponseField name="url" type="string">
      The destination URL for event delivery.
    </ResponseField>

    <ResponseField name="events" type="array">
      The list of event types this webhook is subscribed to.
    </ResponseField>

    <ResponseField name="enabled" type="boolean">
      Whether the webhook is currently active.
    </ResponseField>

    <ResponseField name="created_at" type="string">
      ISO 8601 timestamp of when the webhook was registered.
    </ResponseField>

    <ResponseField name="updated_at" type="string">
      ISO 8601 timestamp of the most recent update.
    </ResponseField>
  </Expandable>
</ResponseField>

***

## Update a webhook

**`PATCH`** **`https://api.chevre.io/v1/webhooks/{id}`**

Updates an existing webhook's URL, event subscriptions, or enabled state. Send only the fields you want to change.

### Path parameters

<ParamField path="id" type="string" required>
  The unique identifier of the webhook to update (e.g. `wh_01RST`).
</ParamField>

### Request body

<ParamField body="url" type="string">
  A new destination URL. Must use `https://`. Chevre will send a test ping to the new URL to verify reachability.
</ParamField>

<ParamField body="events" type="array">
  A replacement list of event type strings. The entire events array is replaced — to add a single event, include all existing events plus the new one.
</ParamField>

<ParamField body="enabled" type="boolean">
  Set to `false` to pause event delivery, or `true` to resume it.
</ParamField>

### Example request

<CodeGroup>
  ```bash cURL theme={null}
  curl --request PATCH \
    --url "https://api.chevre.io/v1/webhooks/wh_01RST" \
    --header "Authorization: Bearer <your_api_key>" \
    --header "Content-Type: application/json" \
    --data '{
      "events": [
        "automation.run.completed",
        "automation.run.failed",
        "member.invited",
        "project.created"
      ],
      "enabled": true
    }'
  ```

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

  response = requests.patch(
      "https://api.chevre.io/v1/webhooks/wh_01RST",
      json={
          "events": [
              "automation.run.completed",
              "automation.run.failed",
              "member.invited",
              "project.created",
          ],
          "enabled": True,
      },
      headers={"Authorization": "Bearer <your_api_key>"},
  )
  print(response.json())
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.chevre.io/v1/webhooks/wh_01RST", {
    method: "PATCH",
    headers: {
      Authorization: "Bearer <your_api_key>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      events: [
        "automation.run.completed",
        "automation.run.failed",
        "member.invited",
        "project.created",
      ],
      enabled: true,
    }),
  });
  const data = await response.json();
  console.log(data);
  ```
</CodeGroup>

### Example response

```json 200 theme={null}
{
  "data": {
    "id": "wh_01RST",
    "url": "https://example.com/chevre-webhook",
    "events": [
      "automation.run.completed",
      "automation.run.failed",
      "member.invited",
      "project.created"
    ],
    "enabled": true,
    "created_at": "2024-04-10T08:00:00Z",
    "updated_at": "2024-06-10T11:30:00Z"
  }
}
```

### Response fields

<ResponseField name="data" type="object">
  The updated webhook object.

  <Expandable title="webhook object">
    <ResponseField name="id" type="string">
      The unique identifier for the webhook.
    </ResponseField>

    <ResponseField name="url" type="string">
      The updated or unchanged destination URL.
    </ResponseField>

    <ResponseField name="events" type="array">
      The updated list of subscribed event types.
    </ResponseField>

    <ResponseField name="enabled" type="boolean">
      The updated enabled state.
    </ResponseField>

    <ResponseField name="created_at" type="string">
      ISO 8601 timestamp of when the webhook was originally registered.
    </ResponseField>

    <ResponseField name="updated_at" type="string">
      ISO 8601 timestamp of this update.
    </ResponseField>
  </Expandable>
</ResponseField>

***

## Delete a webhook

**`DELETE`** **`https://api.chevre.io/v1/webhooks/{id}`**

Permanently deletes a webhook registration. Chevre immediately stops sending event deliveries to this endpoint.

### Path parameters

<ParamField path="id" type="string" required>
  The unique identifier of the webhook to delete (e.g. `wh_01RST`).
</ParamField>

### Example request

<CodeGroup>
  ```bash cURL theme={null}
  curl --request DELETE \
    --url "https://api.chevre.io/v1/webhooks/wh_01RST" \
    --header "Authorization: Bearer <your_api_key>"
  ```

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

  response = requests.delete(
      "https://api.chevre.io/v1/webhooks/wh_01RST",
      headers={"Authorization": "Bearer <your_api_key>"},
  )
  print(response.status_code)  # 204
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.chevre.io/v1/webhooks/wh_01RST", {
    method: "DELETE",
    headers: {
      Authorization: "Bearer <your_api_key>",
    },
  });
  console.log(response.status); // 204
  ```
</CodeGroup>

### Example response

```json 204 theme={null}
// No content returned on successful deletion
```

<Note>
  A successful deletion returns HTTP `204 No Content` with an empty response body.
</Note>

***

## Available webhook events

Subscribe to any combination of the following events when creating or updating a webhook.

<Tabs>
  <Tab title="Automation">
    | Event                      | Description                                         |
    | -------------------------- | --------------------------------------------------- |
    | `automation.run.started`   | An automation run has begun executing.              |
    | `automation.run.completed` | An automation run finished successfully.            |
    | `automation.run.failed`    | An automation run encountered an error and stopped. |
    | `automation.created`       | A new automation was created in a project.          |
    | `automation.updated`       | An automation's configuration was changed.          |
    | `automation.deleted`       | An automation was deleted from a project.           |
  </Tab>

  <Tab title="Member">
    | Event                 | Description                                |
    | --------------------- | ------------------------------------------ |
    | `member.invited`      | A new invitation was sent to a user.       |
    | `member.joined`       | An invited user accepted their invitation. |
    | `member.role_changed` | A member's role was updated.               |
    | `member.removed`      | A member was removed from the workspace.   |
  </Tab>

  <Tab title="Project">
    | Event              | Description                                 |
    | ------------------ | ------------------------------------------- |
    | `project.created`  | A new project was created in the workspace. |
    | `project.updated`  | A project's details were changed.           |
    | `project.archived` | A project was archived.                     |
    | `project.deleted`  | A project was permanently deleted.          |
  </Tab>
</Tabs>

***

## Webhook payload format

Chevre delivers all events as HTTP POST requests with a JSON body. Every payload follows the same top-level envelope structure, with resource-specific data nested inside the `data` field.

```json theme={null}
{
  "id": "evt_01XYZ",
  "type": "automation.run.completed",
  "created_at": "2024-06-01T14:22:00Z",
  "data": {
    "automation_id": "auto_01GHI",
    "run_id": "run_01DEF",
    "status": "success"
  }
}
```

<ResponseField name="id" type="string">
  A unique identifier for this event delivery, prefixed with `evt_`. Use this to deduplicate retried deliveries.
</ResponseField>

<ResponseField name="type" type="string">
  The event type string that triggered this delivery (e.g. `"automation.run.completed"`).
</ResponseField>

<ResponseField name="created_at" type="string">
  ISO 8601 timestamp of when the event occurred in Chevre.
</ResponseField>

<ResponseField name="data" type="object">
  The event-specific payload. The fields inside `data` vary depending on the `type`. Refer to the [Available events](#available-webhook-events) section for per-event data shapes.
</ResponseField>

***

## Signature verification

Every webhook delivery from Chevre includes a `Chevre-Signature` header containing an HMAC-SHA256 signature computed over the raw request body using the secret you provided at registration. Always verify this signature before processing a payload.

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require("crypto");

  function verifyChevreSignature(rawBody, signatureHeader, secret) {
    // Compute expected signature
    const expectedSignature = crypto
      .createHmac("sha256", secret)
      .update(rawBody, "utf8")
      .digest("hex");

    // Compare using a timing-safe comparison to prevent timing attacks
    const trusted = Buffer.from(`sha256=${expectedSignature}`, "utf8");
    const received = Buffer.from(signatureHeader, "utf8");

    if (trusted.length !== received.length) {
      return false;
    }

    return crypto.timingSafeEqual(trusted, received);
  }

  // Express.js example
  app.post("/chevre-webhook", express.raw({ type: "application/json" }), (req, res) => {
    const signature = req.headers["chevre-signature"];
    const isValid = verifyChevreSignature(req.body, signature, process.env.CHEVRE_WEBHOOK_SECRET);

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

    const event = JSON.parse(req.body);
    console.log("Received event:", event.type);

    // Acknowledge receipt immediately
    res.status(200).send("OK");
  });
  ```

  ```python Python theme={null}
  import hashlib
  import hmac
  import os

  def verify_chevre_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
      """Verify the HMAC-SHA256 signature on a Chevre webhook delivery."""
      expected_signature = hmac.new(
          secret.encode("utf-8"),
          raw_body,
          hashlib.sha256,
      ).hexdigest()

      expected = f"sha256={expected_signature}"

      # Use hmac.compare_digest to prevent timing attacks
      return hmac.compare_digest(expected, signature_header)


  # Flask example
  from flask import Flask, request, abort
  import json

  app = Flask(__name__)

  @app.route("/chevre-webhook", methods=["POST"])
  def chevre_webhook():
      raw_body = request.get_data()
      signature = request.headers.get("Chevre-Signature", "")
      secret = os.environ["CHEVRE_WEBHOOK_SECRET"]

      if not verify_chevre_signature(raw_body, signature, secret):
          abort(401)

      event = json.loads(raw_body)
      print(f"Received event: {event['type']}")

      # Acknowledge receipt immediately
      return "OK", 200
  ```
</CodeGroup>

<Note>
  Always use a timing-safe comparison function (such as `crypto.timingSafeEqual` in Node.js or `hmac.compare_digest` in Python) when comparing signatures. A standard equality check is vulnerable to timing attacks.
</Note>

***

## Retry behavior

If your endpoint does not respond with a `2xx` HTTP status code within **10 seconds**, Chevre marks the delivery as failed and retries it automatically.

Chevre retries failed deliveries up to **5 times** using exponential backoff with jitter:

| Attempt   | Delay        |
| --------- | ------------ |
| 1st retry | \~30 seconds |
| 2nd retry | \~5 minutes  |
| 3rd retry | \~30 minutes |
| 4th retry | \~2 hours    |
| 5th retry | \~8 hours    |

After all 5 retries are exhausted, the delivery is marked as permanently failed. You can view delivery attempts and their statuses in the Chevre dashboard under **Settings → Webhooks**.

<Tip>
  Respond to webhook deliveries as quickly as possible with a `200 OK` — ideally before doing any processing. Offload heavy work to a background queue after acknowledging receipt. This prevents Chevre from timing out and triggering unnecessary retries.
</Tip>

You can use the event `id` field to safely deduplicate retried deliveries in case your handler processes the same event more than once.
