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

# Check Webhooks

> Lists all webhooks configured on the instance or returns a single one by label

**Auth:** `TokenAccount` or `TokenInstance` • **Rate limit:** `Global` (100/min) • **Idempotent:** yes

## Description

Read endpoint. Without query, returns **all** webhooks (enabled and disabled) on the instance. With `?label=<name>`, returns the single webhook with that label (or `404`).

## Examples

### List all

Without a query string, returns the `webhooks[]` array with all webhooks on the instance (enabled and disabled), sorted alphabetically by `label`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://ryzeapi.cloud/api/events/getWebhook/$Instance_Name" \
    -H "token: $Token_Instance"
  ```

  ```javascript JavaScript theme={null}
  await fetch(`https://ryzeapi.cloud/api/events/getWebhook/${process.env.Instance_Name}`, {
    method: "GET",
    headers: {
      "token": process.env.Token_Instance
    }
  });
  ```

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

  requests.get(
      f"https://ryzeapi.cloud/api/events/getWebhook/{os.environ['Instance_Name']}",
      headers={
          "token": os.environ["Token_Instance"]
      }
  )
  ```

  ```go Go theme={null}
  package main

  import (
      "net/http"
      "os"
  )

  func main() {
      req, _ := http.NewRequest("GET", "https://ryzeapi.cloud/api/events/getWebhook/"+os.Getenv("Instance_Name"), nil)
      req.Header.Set("token", os.Getenv("Token_Instance"))
      http.DefaultClient.Do(req)
  }
  ```
</CodeGroup>

### A specific one

Passing `?label=analytics-pipeline`, returns just the webhook with that label in the envelope's `webhook` field (or `404` if it doesn't exist).

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://ryzeapi.cloud/api/events/getWebhook/$Instance_Name?label=analytics-pipeline" \
    -H "token: $Token_Instance"
  ```

  ```javascript JavaScript theme={null}
  await fetch(`https://ryzeapi.cloud/api/events/getWebhook/${process.env.Instance_Name}?label=analytics-pipeline`, {
    method: "GET",
    headers: {
      "token": process.env.Token_Instance
    }
  });
  ```

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

  requests.get(
      f"https://ryzeapi.cloud/api/events/getWebhook/{os.environ['Instance_Name']}?label=analytics-pipeline",
      headers={
          "token": os.environ["Token_Instance"]
      }
  )
  ```

  ```go Go theme={null}
  package main

  import (
      "net/http"
      "os"
  )

  func main() {
      req, _ := http.NewRequest("GET", "https://ryzeapi.cloud/api/events/getWebhook/"+os.Getenv("Instance_Name")+"?label=analytics-pipeline", nil)
      req.Header.Set("token", os.Getenv("Token_Instance"))
      http.DefaultClient.Do(req)
  }
  ```
</CodeGroup>

### Explicit default

Fetches the default webhook by passing `?label=default`. Useful when you created the webhook without specifying a `label` and want to read just that entry instead of the full list.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://ryzeapi.cloud/api/events/getWebhook/$Instance_Name?label=default" \
    -H "token: $Token_Instance"
  ```

  ```javascript JavaScript theme={null}
  await fetch(`https://ryzeapi.cloud/api/events/getWebhook/${process.env.Instance_Name}?label=default`, {
    method: "GET",
    headers: {
      "token": process.env.Token_Instance
    }
  });
  ```

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

  requests.get(
      f"https://ryzeapi.cloud/api/events/getWebhook/{os.environ['Instance_Name']}?label=default",
      headers={
          "token": os.environ["Token_Instance"]
      }
  )
  ```

  ```go Go theme={null}
  package main

  import (
      "net/http"
      "os"
  )

  func main() {
      req, _ := http.NewRequest("GET", "https://ryzeapi.cloud/api/events/getWebhook/"+os.Getenv("Instance_Name")+"?label=default", nil)
      req.Header.Set("token", os.Getenv("Token_Instance"))
      http.DefaultClient.Do(req)
  }
  ```
</CodeGroup>

## Success response

Without `?label=`, returns `webhooks[]` (sorted alphabetically by `label`, always present, comes back as `[]` if no webhook exists, and **also includes the ones with `enabled=false`** for operational inspection). With `?label=<name>`, returns the single object in `webhook` (same shape as `POST`). The `authorization` is decrypted when `ENCRYPTION_KEY` is configured; if the key was rotated and some value cannot be decrypted, the field is returned encrypted instead of failing the request.

```json 200 OK (list, no ?label=) theme={null}
{
  "success": true,
  "message": "Webhook configurations retrieved",
  "webhooks": [
    {
      "label":         "analytics-pipeline",
      "enabled":       true,
      "url":           "https://analytics.meuapp.com/events",
      "authorization": "Bearer svc-token-xyz",
      "byEvents":      false,
      "events":        ["message.exchange", "message.status"],
      "mediaBase64":   false
    },
    {
      "label":       "default",
      "enabled":     true,
      "url":         "https://meuapp.com/webhook",
      "byEvents":    false,
      "events":      [],
      "mediaBase64": false
    },
    {
      "label":       "legacy",
      "enabled":     false,
      "url":         "",
      "byEvents":    false,
      "events":      [],
      "mediaBase64": false
    }
  ]
}
```

```json 200 OK (?label=analytics-pipeline) theme={null}
{
  "success": true,
  "message": "Webhook configuration retrieved",
  "webhook": {
    "label":         "analytics-pipeline",
    "enabled":       true,
    "url":           "https://analytics.meuapp.com/events",
    "authorization": "Bearer svc-token-xyz",
    "byEvents":      false,
    "events":        ["message.exchange", "message.status"],
    "mediaBase64":   false
  }
}
```

<ResponseField name="webhooks" type="array">
  Present only when there is **no** `?label=`. Sorted alphabetically by `label`. Always returned even when no webhook exists (`webhooks: []`).
</ResponseField>

<ResponseField name="webhook" type="object">
  Present only when `?label=<name>` is used. Same shape as `POST`.
</ResponseField>

## Path parameters

<ParamField path="instance" type="string" required>
  Instance name.
</ParamField>

## Headers

<ParamField header="token" type="string" required>
  `TokenAccount` or `TokenInstance`.
</ParamField>

## Query parameters

<ParamField query="label" type="string">
  When present, returns a single webhook (`webhook` in the envelope). When absent, returns the list (`webhooks[]`).

  Passing `?label=` (empty) is still considered "present" → it becomes `"default"` and looks up the row with that label.
</ParamField>

<Note>
  * **Listing includes `enabled=false`**, operators see the full history. To list only active ones, filter on the client by `w.enabled === true`.
  * **`authorization` decrypted**: if `ENCRYPTION_KEY` is configured and the value is encrypted at rest, the repository decrypts it before returning. If the key was rotated and a value cannot be decrypted, the field is returned encrypted (with a warning in the log) instead of failing the request.
</Note>

***

## Delivery: queue, retry, DLQ

Webhook delivery is **async** and persisted. Every event that matches a webhook is enqueued in `webhook_queue` and processed by parallel workers.

### Flow

```
EventsService.sendWebhook()
   └─► webhook_queue.Enqueue()        [INSERT row, status=pending]
                                          │
WebhookDispatcher (4 workers + janitor) ──┘
   ├─► ClaimNext()  (FOR UPDATE SKIP LOCKED)
   ├─► HTTP POST + SSRF re-check
   ├─► MarkDelivered (2xx)
   ├─► MarkRetry (4xx except 408/429, redeliver; 408/429/5xx, retry)
   └─► MarkFailed (attempts > max_attempts → DLQ)

Janitor: every 15min, DELETE rows delivered > 24h ago.
```

### Exponential backoff

| Attempt  | Next retry |
| -------- | ---------- |
| 1 (fail) | +1s        |
| 2 (fail) | +5s        |
| 3 (fail) | +30s       |
| 4 (fail) | +5min      |
| 5 (fail) | +30min     |
| 6+       | +1h (cap)  |

After `max_attempts` (default 5), `status` becomes `failed` (DLQ). The row is **not** deleted automatically, operators can inspect `last_error` and re-enqueue manually (`UPDATE webhook_queue SET status='pending', next_retry_at=now()`).

### `webhook_queue` table (ops summary)

| Column                      | Description                                                         |
| --------------------------- | ------------------------------------------------------------------- |
| `id`                        | `BIGSERIAL` PK                                                      |
| `instance_name`             | Instance name                                                       |
| `url`                       | Final destination (already with `/event-name` suffix if `byEvents`) |
| `payload`                   | `BYTEA`, JSON body of the event                                     |
| `auth_header`               | Configured `Authorization` (encrypted at rest)                      |
| `event_type`                | Event name (`message.exchange` etc.)                                |
| `status`                    | `pending` \| `delivered` \| `failed`                                |
| `attempts`                  | Attempt counter                                                     |
| `max_attempts`              | Default 5                                                           |
| `next_retry_at`             | Next attempt window                                                 |
| `last_error`                | Last error message from the consumer                                |
| `created_at` / `updated_at` | Timestamps                                                          |

## Delivered headers

Each `POST` to your webhook arrives with:

```http theme={null}
POST <url> HTTP/1.1
Host: <your-host>
Content-Type: application/json
Authorization: <config.authorization>      # if configured
User-Agent: RyzeAPI/<version>

{ "event": "...", "data": { ... }, "instanceData": { ... } }
```

<Warning>
  **There is no automatic HMAC.** Origin validation is the consumer's responsibility, configure an `authorization` (Bearer token, API key) and validate it on your endpoint.
</Warning>

## Errors

| HTTP | `error.message`                                              | Applies to |
| ---- | ------------------------------------------------------------ | ---------- |
| 400  | `label too long (max 50 chars)`                              | `?label=`  |
| 400  | `label may only contain letters, digits, underscore or dash` | `?label=`  |
| 401  | `Invalid token`                                              | both       |
| 404  | `Instance not found`                                         | both       |
| 404  | `Webhook not configured for this label`                      | `?label=`  |
| 429  | `Rate limit exceeded. Try again later.`                      | both       |
| 500  | `Failed to get instance`                                     | both       |
| 500  | `Failed to list webhook configurations`                      | no query   |

Envelope:

```json theme={null}
{
  "success": false,
  "error": { "message": "Webhook not configured for this label" }
}
```

## Next

<CardGroup cols={2}>
  <Card title="Configure webhook" icon="webhook" href="/en/api/events/webhook-configure">
    `POST /api/events/webhook/:instance`
  </Card>

  <Card title="Event catalog" icon="book" href="/en/api/events/catalog">
    Schemas of the 6 event types.
  </Card>
</CardGroup>
