> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.jelliu.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.jelliu.co/_mcp/server.

# List webhooks

GET https://api.jelliu.co/api/webhooks

Returns the workspace's outbound webhooks, newest first, including disabled ones. Deleted webhooks
are not returned. The list is not paginated and holds at most 100 webhooks.

Each object is the full webhook, including `filters`, custom `headers` (in plaintext) and the last
25 delivery attempts in `delivery_logs`. The signing secret is always masked as `"[configured]"`.
To look at one webhook, use `GET /api/webhooks/{webhookId}`.

**Idempotency.** A read with no side effects. Safe to retry.

**Access**

* **Required scope:** `read` or `write`. Any workspace role can call it.
* **Rate limit:** General API — 120 requests/min per workspace on Starter or with no active plan, 200 on Growth, 300 on Business, 600 on Enterprise. See [Rate limits](/rate-limits).
* **Plan:** Requires the `webhook` plan feature. Every plan includes it today, including workspaces without an active plan, so this gate currently refuses nobody.

Reference: https://developer.jelliu.co/api-reference/webhooks/get-webhooks

## Authentication

- `Authorization` header (bearer token, required) — Workspace API key: `jl_` followed by 64 lowercase hex characters, created by the workspace owner in the dashboard (**Settings → API Keys**) and sent as `Authorization: Bearer jl_...`. The plaintext is shown once, at creation; Jelliu stores only a SHA-256 hash. A workspace can hold up to 25 active keys. | Scope | GET / HEAD | POST / PUT / PATCH / DELETE | Admin-only routes | | --- | --- | --- | --- | | `read` | Yes | No | No | | `write` | Yes | Yes | No | | `full` | Yes | Yes | Yes | Operations restricted to admins or owners reject keys without the `full` scope with `403`, and say so in their description. No key, whatever its scope, can mint or revoke API keys or rotate a webhook secret — that requires a signed-in owner session. A revoked key stops authenticating within about 10 seconds. See [Authentication](/authentication).

## Response

### 200

The workspace's webhooks, newest first.

- `data` (list of object, required) — Webhooks ordered by `created_at` descending. Empty array when there are none.
  - `id` (string, required) — Unique identifier of the webhook. Use it as `webhookId` in the other webhook endpoints.
  - `tenant_id` (string, required) — The workspace that owns the webhook.
  - `url` (string, required) — Endpoint that receives the `POST` deliveries.
  - `secret` (string, required) — `"[configured]"` everywhere except the create response, where it is the plaintext signing secret (`whsec_` followed by 48 hex characters). Store it then; it cannot be read back.
  - `events` (list of enum, required) — Subscribed event names. `"*"` means every event except `audit.log_recorded`.
    - Allowed values: `*`, `call.started`, `call.completed`, `call.failed`, `call.recording_ready`, `call.transcript_ready`, `agent.created`, `agent.updated`, `agent.action_recorded`, `audit.log_recorded`, `campaign.started`, `campaign.paused`, `campaign.completed`, `contact.created`, `contact.updated`, `contact.status_changed`, `contact.converted`, `contact.dnc`, `usage.threshold_reached`, `subscription.changed`, `integration.sync_completed`, `integration.sync_failed`, `integration.error`, `crm_sync.completed`, `crm_sync.failed`
  - `is_active` (boolean, required) — `false` after a `PATCH` with `is_active: false`, or after the webhook was disabled automatically once 10 consecutive events failed. Inactive webhooks receive nothing.
  - `failure_count` (integer, required) — Consecutive events that failed for good. Reset to 0 by any 2xx delivery and by re-enabling the webhook. At 10 the webhook is disabled.
  - `created_at` (datetime, required) — When the webhook was created.
  - `updated_at` (datetime, required) — Last change to the row, including delivery bookkeeping (logs, counters), not only your edits.
  - `description` (string, optional, nullable) — Free-text label shown in the dashboard, up to 500 characters.
  - `filters` (object, optional, nullable) — Delivery filters, or null when every subscribed event is delivered. See `WebhookFilters`.
    - `campaignIds` (list of string, optional) — Deliver only events whose campaign is in this list (up to 50 UUIDs). Events without a campaign are dropped.
    - `agentIds` (list of string, optional) — Deliver only events whose agent is in this list (up to 50 UUIDs). Events without an agent are dropped.
    - `outcomes` (list of string, optional) — Deliver only events whose outcome is in this list (up to 20 values, 100 characters each). Events without an outcome are dropped.
    - `minSentiment` (double, optional) — Deliver only events whose sentiment score is at least this value (-1 to 1). Events without a score are dropped.
    - `auditActions` (list of string, optional) — Only for `audit.log_recorded`: action prefixes, combined with OR (for example `payments.`, or `FAILED_` for refused attempts only). Each prefix is tested against the action with and without its `FAILED_MUTATION:` / `FAILED_READ:` prefix. Ignored for every other event.
    - `conditions` (list of object, optional) — Custom conditions on the payload, up to 10. All must hold. `field` is a dot path inside `data`.
      - `field` (string, required) — Dot path inside the event's `data`.
      - `operator` (enum, required) — `eq`/`neq` compare for equality; `gt`/`lt`/`gte`/`lte` compare numbers only; `contains` tests a substring on strings only; `exists` checks that the field is present.
        - Allowed values: `eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `contains`, `exists`
      - `value` (string or double or boolean, optional) — Value to compare with. Not used by `exists`.
  - `headers` (map from string to string, optional, nullable) — Custom headers added to every delivery, returned in plaintext. Null when none are set.
  - `payload_template` (string, optional, nullable) — Custom JSON body template, or null for the default `{event, timestamp, data}` body. See `CreateWebhookInput.payload_template`.
  - `delivery_logs` (list of object, optional, nullable) — The last delivery attempts (at most 25), newest first. Same rows as `GET /api/webhooks/{webhookId}/delivery-logs`. Null until the first attempt.
    - `event` (enum, required) — Outbound webhook event catalog, plus `*` (every event except `audit.log_recorded`). Every value is accepted in a subscription, but only these events are delivered today: `call.completed`, `call.failed`, `campaign.completed`, `crm_sync.completed`, `crm_sync.failed`, `agent.action_recorded` and `audit.log_recorded`. The rest are reserved and are not sent yet.
      - Allowed values: `*`, `call.started`, `call.completed`, `call.failed`, `call.recording_ready`, `call.transcript_ready`, `agent.created`, `agent.updated`, `agent.action_recorded`, `audit.log_recorded`, `campaign.started`, `campaign.paused`, `campaign.completed`, `contact.created`, `contact.updated`, `contact.status_changed`, `contact.converted`, `contact.dnc`, `usage.threshold_reached`, `subscription.changed`, `integration.sync_completed`, `integration.sync_failed`, `integration.error`, `crm_sync.completed`, `crm_sync.failed`
    - `status` (integer, required) — HTTP status your endpoint answered. `0` means no response: a timeout, a connection or DNS failure, or a delivery refused before sending (invalid custom headers, a `payload_template` that did not render).
    - `duration_ms` (integer, required) — Time from the start of the attempt to the response or failure, in milliseconds. `0` for deliveries refused before sending.
    - `delivered_at` (datetime, required) — The event's timestamp (the same value as `X-Webhook-Timestamp`), not the time of this attempt. Retries of one event share it.
    - `error` (string, optional) — Present on failures only. Formats include `HTTP <status>: [remote-error] <body>` (body sanitized and truncated to 500 characters) and network or timeout messages.
  - `last_triggered_at` (datetime, optional, nullable) — When a delivery last succeeded or finally failed. Null until then.
  - `last_error` (string, optional, nullable) — Error of the most recent final failure. Cleared by a success or by re-enabling.
  - `deleted_at` (datetime, optional, nullable) — Always `null` in responses; deleted webhooks are not returned.

## Errors

### 401 Unauthorized Error

No usable credential. Either the `Authorization` header is missing or is not a well-formed `Bearer jl_…` key, or the key is unknown, revoked or expired. Do not retry with the same key. See [Authentication](/authentication#401-unauthorized).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 403 Forbidden Error

The API key lacks the `read` scope, the workspace is suspended, or the plan lacks outbound webhooks (not possible on any plan today).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 429 Too Many Requests Error

General API rate limit exceeded.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

## Examples

**Response**

```json
{
  "data": [
    {
      "id": "5d2f8a4c-1b3e-4f6a-9c7d-8e0f1a2b3c4d",
      "tenant_id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
      "url": "https://hooks.inmobiliariaandina.co/jelliu/llamadas",
      "secret": "[configured]",
      "events": [
        "call.completed",
        "call.failed"
      ],
      "is_active": true,
      "failure_count": 0,
      "created_at": "2026-09-01T13:20:00.000Z",
      "updated_at": "2026-09-14T15:04:05.402Z",
      "description": "Sincronizar llamadas con el CRM",
      "filters": {
        "outcomes": [
          "interested",
          "appointment_booked"
        ]
      },
      "headers": {
        "Authorization": "Bearer rcv_8f3a2c1d9e"
      },
      "payload_template": null,
      "delivery_logs": [
        {
          "event": "call.completed",
          "status": 200,
          "duration_ms": 184,
          "delivered_at": "2026-09-14T15:04:05.123Z"
        }
      ],
      "last_triggered_at": "2026-09-14T15:04:05.402Z",
      "last_error": null,
      "deleted_at": null
    },
    {
      "id": "3c9e1f7a-6b2d-4e8a-a5c4-1f0b2d3e4a5b",
      "tenant_id": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
      "url": "https://siem.inmobiliariaandina.co/ingest/jelliu",
      "secret": "[configured]",
      "events": [
        "audit.log_recorded"
      ],
      "is_active": false,
      "failure_count": 10,
      "created_at": "2026-08-20T10:00:00.000Z",
      "updated_at": "2026-08-30T09:13:59.871Z",
      "description": "Auditoría hacia el SIEM",
      "filters": {
        "auditActions": [
          "webhook.",
          "FAILED_"
        ]
      },
      "headers": {},
      "payload_template": null,
      "delivery_logs": [
        {
          "event": "audit.log_recorded",
          "status": 0,
          "duration_ms": 10003,
          "delivered_at": "2026-08-30T09:12:44.010Z",
          "error": "The operation was aborted due to timeout"
        }
      ],
      "last_triggered_at": "2026-08-30T09:13:59.871Z",
      "last_error": "The operation was aborted due to timeout",
      "deleted_at": null
    }
  ]
}
```

**SDK Code**

```python Webhooks_getWebhooks_example
import requests

url = "https://api.jelliu.co/api/webhooks"

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript Webhooks_getWebhooks_example
const url = 'https://api.jelliu.co/api/webhooks';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Webhooks_getWebhooks_example
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/webhooks"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Webhooks_getWebhooks_example
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/webhooks")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java Webhooks_getWebhooks_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.jelliu.co/api/webhooks")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php Webhooks_getWebhooks_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.jelliu.co/api/webhooks', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp Webhooks_getWebhooks_example
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/webhooks");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Webhooks_getWebhooks_example
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/webhooks")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```