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

# Gatilhos e webhooks

> Avisos assinados quando algo acontece na memória. A Niadra avisa; quem age é o seu sistema.

A Niadra avisa os seus sistemas quando algo acontece na memória, por webhooks assinados. Há dois tipos. Uma **assinatura de webhook** entrega cada ocorrência de um tipo de evento, como `action.recorded`. Um **gatilho** é uma regra sua sobre o que a memória sabe, com parâmetros, janela e deduplicação por sujeito, como "promessa da empresa vencida há um dia". Nos dois casos, a Niadra avisa por webhook, e quem age é o seu agente ou o seu sistema. Gatilho nunca chama sistema de registro, nunca abre ticket, nunca manda mensagem ao cliente final e nunca ramifica.

## Tipos de evento de saída

| Tipo                                    | Quando é enviado                                                                                            |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `memory.updated`                        | A memória derivada de um perfil mudou                                                                       |
| `identity.merged` / `identity.unmerged` | Dois perfis foram unidos, ou separados                                                                      |
| `identity.suggestion`                   | Uma possível ligação entre handles aguarda revisão                                                          |
| `open_item.due` / `open_item.closed`    | Uma pendência chegou ao prazo, ou foi fechada por uma ação                                                  |
| `action.recorded`                       | Um agente registrou o que fez num sistema de registro                                                       |
| `trait.added` / `trait.expired`         | Um padrão apareceu num perfil, ou deixou de valer                                                           |
| `trigger.fired` / `trigger.retracted`   | Uma regra sua disparou, ou um disparo foi retirado depois que uma separação de perfis dividiu as evidências |
| `export.completed` / `export.failed`    | Uma execução da exportação contínua terminou                                                                |
| `erasure.completed`                     | Um apagamento terminou, com os ids apagados e as execuções de exportação que já os continham                |
| `usage.threshold`                       | O uso passou de um limite definido por você                                                                 |

Endpoints, assinaturas e regras de gatilho são configuração versionada na API de controle, alterada por diffs que uma pessoa aprova. Todo endpoint pertence a uma fonte com classe de audiência, e uma regra só é aceita, e só dispara, se a condição e o conteúdo do aviso puderem ser lidos por essa fonte. `trait.present(overdue_invoices)` nunca chega a um sistema de marketing.

## O catálogo de gatilhos

| Condição                                  | Dispara quando                                                                           |
| ----------------------------------------- | ---------------------------------------------------------------------------------------- |
| `promise.overdue(days)`                   | Uma promessa da empresa está vencida há o número de dias informado, sem ação que a feche |
| `open_item.due(lead_time)`                | Uma pendência está perto do prazo                                                        |
| `trait.present(name, filters)`            | Um perfil tem determinado padrão                                                         |
| `sentiment.drop`                          | O sentimento das sessões recentes caiu abaixo do limiar                                  |
| `identity.ambiguous`                      | Um handle passou a ligar perfis demais, como o tablet compartilhado de uma loja          |
| `fact.contradicted_by_system_event`       | Uma ação declarada não foi confirmada pelo sistema de registro dentro da janela          |
| `recontact(72h)`                          | O cliente voltou em até 72 horas pela mesma categoria                                    |
| `context_use.repetition_rate(source) > X` | A taxa de repetição de uma fonte passou de X na janela                                   |
| `source.silent(minutes)`                  | Uma fonte parou de mandar eventos                                                        |

Condições novas entram pelo catálogo do produto, nunca por texto livre. Uma regra dispara no máximo uma vez por sujeito por janela (24 horas por padrão), com teto de 100 disparos por minuto por espaço e de 50 regras por espaço. Condições por evento são avaliadas logo depois que a memória é atualizada; condições por tempo, a cada 5 minutos. Um disparo atrasado sai marcado como `late` ou é suprimido, conforme a regra.

<Tip>
  Antes de ligar uma regra, rode um teste a seco por [`POST /v1/triggers/dry-run`](/api/triggers-dry-run): a regra em teste traz `rule_id`, `condition`, `params`, o `endpoint_id` de destino, `window_hours` (24 por padrão, até 90 dias) e `late_policy` (`deliver_marked` ou `suppress`). A resposta, sobre os últimos 30 dias, diz quantos disparos teriam acontecido (`would_fire`), com uma amostra, e traz `target_problem` quando a fonte do endpoint não pode ler o conteúdo do aviso. Ajuste os parâmetros antes de acionar qualquer pessoa. Pede o papel `integration` ou uma chave `admin`.
</Tip>

Exemplo: "avisar o CRM um dia depois de uma visita técnica perdida". Quando a visita remarcada da Marina passa um dia do prazo sem ação que a feche, a Niadra manda `trigger.fired` para o endpoint do CRM, e o agente do seu CRM remarca a visita.

## Como é uma entrega

As entregas seguem o formato aberto **Standard Webhooks**.

```json theme={null}
{
  "id": "msg_01J8ZV",
  "type": "trigger.fired",
  "created_at": "2026-09-23T15:05:00Z",
  "tenant": "acme",
  "subject": { "pseudonym": "psn_7f3a", "kind": "person", "system_id": "48213" },
  "data": {
    "rule_id": "rule_overdue_visit",
    "rule_version": 3,
    "condition": "promise.overdue",
    "evidence": ["oi_01J8ZK"],
    "as_of": "2026-09-23T15:04:58Z"
  },
  "links": { "open": "/v1/history/items/oi_01J8ZK" }
}
```

O corpo leva ids, a regra e a versão, estado e ponteiros para as evidências. **Nunca** leva conteúdo de conversa. O sujeito pode trazer o `system_id` do cliente no seu próprio sistema, nunca telefone, e-mail ou número de documento. `links.open` é a rota da API que abre o item, e ela só funciona com uma credencial do seu espaço que tenha o escopo para isso.

Toda entrega traz três cabeçalhos:

* `webhook-id`: único por mensagem e igual em todas as tentativas. Use para deduplicar.
* `webhook-timestamp`: segundos Unix. Recuse o que estiver a mais de 5 minutos do seu relógio.
* `webhook-signature`: `v1,` seguido do HMAC-SHA256 em base64 de `{webhook-id}.{webhook-timestamp}.{corpo}`. Durante a rotação do segredo, duas assinaturas separadas por espaço valem ao mesmo tempo.

## Confira a assinatura

O segredo é gravado uma vez, pelo Console ou por [`PUT /v1/secrets/webhook/{id}`](/api/secrets), e começa com `whsec_`; a chave é a parte em base64 depois do prefixo. Confira sobre o corpo cru, antes de interpretar o JSON.

<CodeGroup>
  ```python Python theme={null}
  import base64
  import hashlib
  import hmac
  import time


  def verify(secret: str, headers: dict[str, str], body: bytes) -> bool:
      msg_id = headers["webhook-id"]
      timestamp = headers["webhook-timestamp"]
      if abs(time.time() - int(timestamp)) > 300:
          return False
      key = base64.b64decode(secret.removeprefix("whsec_"))
      signed = f"{msg_id}.{timestamp}.".encode() + body
      expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
      for candidate in headers["webhook-signature"].split(" "):
          version, _, signature = candidate.partition(",")
          if version == "v1" and hmac.compare_digest(signature, expected):
              return True
      return False
  ```

  ```typescript TypeScript theme={null}
  import { createHmac, timingSafeEqual } from "node:crypto";

  export function verify(secret: string, headers: Record<string, string>, body: string): boolean {
    const id = headers["webhook-id"];
    const timestamp = headers["webhook-timestamp"];
    if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
    const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
    const expected = createHmac("sha256", key).update(`${id}.${timestamp}.${body}`).digest("base64");
    return headers["webhook-signature"].split(" ").some((candidate) => {
      const [version, signature] = candidate.split(",");
      if (version !== "v1" || !signature || signature.length !== expected.length) return false;
      return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
    });
  }
  ```
</CodeGroup>

## Garantias de entrega

* **Pelo menos uma vez.** Uma mensagem pode chegar mais de uma vez; deduplique pelo `webhook-id`.
* **Sem garantia de ordem.** As mensagens podem chegar fora de ordem; cada evento traz `as_of` e versão para você ordenar.
* **Novas tentativas** com recuo exponencial por até 24 horas. Depois disso, a entrega vai para a fila de mortos, visível no Console e guardada por 7 dias.
* **Endpoint que falha sem parar por 24 horas é desativado**, e o seu espaço recebe o aviso.
* **Saída controlada.** Toda chamada para fora passa por um proxy de saída com lista de permissão por espaço. Endereços privados, de metadados da nuvem e de loopback são bloqueados, o DNS é resolvido e fixado a cada tentativa, e só sai HTTPS.

Responda com qualquer 2xx rápido e faça o trabalho de forma assíncrona. Uma entrega fica `pending`, `delivered`, `failing` enquanto tenta de novo, ou `dead`. Essas rotas pedem uma pessoa do Console com o papel `integration` (os disparos aceitam também `analysis`) ou uma chave de escopo `admin`. Para inspecionar entregas, use [Entregas de webhook](/api/webhook-deliveries); para mandar de novo uma entrega morta, com o mesmo `webhook-id`, use [Reenviar uma entrega](/api/webhook-redeliver). Os disparos, com a regra, a versão e as evidências, estão em [Disparos de gatilho](/api/trigger-firings).

## Próximos passos

<CardGroup cols={2}>
  <Card title="Padrões" href="/concepts/patterns">
    os sinais que `trait.present` pode observar.
  </Card>

  <Card title="Webhooks dos seus sistemas" href="/guides/system-webhooks">
    o outro sentido, eventos chegando.
  </Card>

  <Card title="Aproveitamento do contexto" href="/concepts/context-use">
    a taxa de repetição por trás de `context_use.repetition_rate`.
  </Card>

  <Card title="Comprovantes e auditoria" href="/concepts/receipts">
    comprovantes entregues ao seu SIEM pelo mesmo mecanismo.
  </Card>
</CardGroup>
