> ## 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.

# Verificar Webhooks

> Lista todos os webhooks configurados na instância ou retorna um único por label

**Auth:** `TokenAccount` ou `TokenInstance` • **Rate-limit:** `Global` (100/min) • **Idempotente:** sim

## Descrição

Endpoint de leitura. Sem query, retorna **todos** os webhooks (habilitados e desabilitados) da instância. Com `?label=<nome>`, retorna o único webhook desse label (ou `404`).

## Exemplos

### Listar todos

Sem query string, retorna o array `webhooks[]` com todos os webhooks da instância (habilitados e desabilitados), ordenado alfabeticamente por `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>

### Um específico

Passando `?label=analytics-pipeline`, retorna apenas o webhook desse label no campo `webhook` do envelope (ou `404` se não existir).

<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>

### Default explícito

Busca o webhook padrão informando `?label=default`. Útil quando você criou o webhook sem especificar `label` e quer ler somente essa entrada em vez da lista completa.

<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>

## Resposta de sucesso

Sem `?label=`, retorna `webhooks[]` (ordenado alfabeticamente por `label`, sempre presente, vem `[]` se nenhum webhook existir, e **inclui também os com `enabled=false`** para inspeção operacional). Com `?label=<nome>`, retorna o objeto único em `webhook` (mesmo shape do `POST`). O `authorization` vem descriptografado quando `ENCRYPTION_KEY` está configurada; se a chave foi rotacionada e algum valor não decifra, o campo retorna criptografado em vez de derrubar o request.

```json 200 OK (lista, sem ?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">
  Presente apenas quando **não** há `?label=`. Ordenado alfabeticamente por `label`. Sempre retornado mesmo sem nenhum webhook (`webhooks: []`).
</ResponseField>

<ResponseField name="webhook" type="object">
  Presente apenas quando há `?label=<nome>`. Mesmo shape do `POST`.
</ResponseField>

## Parâmetros de rota

<ParamField path="instance" type="string" required>
  Nome da instância.
</ParamField>

## Headers

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

## Query parameters

<ParamField query="label" type="string">
  Quando presente, retorna um único webhook (`webhook` no envelope). Ausente, retorna a lista (`webhooks[]`).

  Passar `?label=` (vazio) ainda é considerado "presente" → vira `"default"` e busca a linha desse label.
</ParamField>

<Note>
  * **Listagem inclui `enabled=false`**, operadores veem o histórico completo. Para listar só ativos, filtre no cliente por `w.enabled === true`.
  * **`authorization` descriptografado**: se `ENCRYPTION_KEY` está configurada e o valor estiver criptografado at-rest, o repositório descriptografa antes de retornar. Se a chave foi rotacionada e algum valor não decifra, o campo retorna criptografado (com warning no log) em vez de derrubar o request.
</Note>

***

## Entrega: queue, retry, DLQ

A entrega de webhooks é **assíncrona** e persistida. Cada evento que matcha um webhook é enfileirado em `webhook_queue` e processado por workers em paralelo.

### Fluxo

```
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 exceto 408/429, reentregar; 408/429/5xx, retry)
   └─► MarkFailed (attempts > max_attempts → DLQ)

Janitor: a cada 15min, DELETE de rows delivered há > 24h.
```

### Backoff exponencial

| Tentativa | Próximo retry |
| --------- | ------------- |
| 1 (fail)  | +1s           |
| 2 (fail)  | +5s           |
| 3 (fail)  | +30s          |
| 4 (fail)  | +5min         |
| 5 (fail)  | +30min        |
| 6+        | +1h (cap)     |

Após `max_attempts` (default 5), `status` vira `failed` (DLQ). A linha **não é** deletada automaticamente, operadores podem inspecionar `last_error` e re-enfileirar manualmente (`UPDATE webhook_queue SET status='pending', next_retry_at=now()`).

### Tabela `webhook_queue` (resumo para ops)

| Coluna                      | Descrição                                                 |
| --------------------------- | --------------------------------------------------------- |
| `id`                        | `BIGSERIAL` PK                                            |
| `instance_name`             | Nome da instância                                         |
| `url`                       | Destino final (já com sufixo `/event-name` se `byEvents`) |
| `payload`                   | `BYTEA`, corpo JSON do evento                             |
| `auth_header`               | `Authorization` configurado (encriptado at-rest)          |
| `event_type`                | Nome do evento (`message.exchange` etc.)                  |
| `status`                    | `pending` \| `delivered` \| `failed`                      |
| `attempts`                  | Contador de tentativas                                    |
| `max_attempts`              | Default 5                                                 |
| `next_retry_at`             | Próxima janela de tentativa                               |
| `last_error`                | Última mensagem de erro do consumer                       |
| `created_at` / `updated_at` | Timestamps                                                |

## Headers entregues

Cada `POST` ao seu webhook chega com:

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

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

<Warning>
  **Não há HMAC automático.** A validação de origem é responsabilidade do consumidor, configure um `authorization` (Bearer token, API key) e valide no seu endpoint.
</Warning>

## Erros

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

Envelope:

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

## Próximo

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

  <Card title="Catálogo de eventos" icon="book" href="/pt/api/events/catalog">
    Schemas dos 6 tipos de evento.
  </Card>
</CardGroup>
