# Cadastra ou muda o webhook da empresa

**PUT** `/v1/webhook-endpoint`

Cria a conexão `custom_api` se ela ainda não existe. Na primeira vez, `secret` traz o `whsec_` UMA vez: guarde para conferir a `Kernos-Signature`. Depois, `secret` é nulo; para trocar, mande `rotate_secret: true`. `status: active` religa o endpoint que desligou por falha.

> **Ação da chave: `integration.webhook.save`** Quem cria a chave escolhe as ações que ela abre; sem esta, a rota recusa com `api_key_action_not_granted`.

> **Toda escrita pede `Idempotency-Key`** Um UUID por escrita. Repetida com o mesmo corpo, devolve a mesma resposta sem escrever de novo. Os SDKs mandam sozinhos.

## Corpo

- `events` (lista de string, obrigatório): Os tipos de evento públicos que o endereço recebe.
- `grace_hours` (integer): Com `rotate_secret`, por quantas horas o segredo antigo continua valendo (0 corta na hora). Nesse tempo a assinatura traz duas `v1`: o novo e o antigo.
- `rotate_secret` (boolean): Troca o `whsec_`; o novo volta em `secret`.
- `status` ("active" | "disabled"): `active` por padrão.
- `url` (string, obrigatório): HTTPS público, sem usuário e senha, até 500 caracteres.

## Devolve

O `data` do envelope `{ ok, data, error, error_code, field, trace_id }`:

- `endpoint` (objeto, ou nulo, obrigatório)
  - `created_at` (data e hora, obrigatório)
  - `disabled_reason` (string, ou nulo, obrigatório)
  - `events` (lista de string, obrigatório)
  - `failing_since` (data e hora, ou nulo, obrigatório)
  - `id` (uuid, obrigatório)
  - `status` ("active" | "disabled", obrigatório)
  - `updated_at` (data e hora, obrigatório)
  - `url` (string, obrigatório)
- `secret` (string, ou nulo, obrigatório): O `whsec_`, só na primeira gravação e no `rotate_secret`.

## Recusas

### HTTP 400 · Pedido mal formado

- [`invalid_idempotency_key`](/erros/invalid_idempotency_key.md) (HTTP 400): Toda escrita pede o cabeçalho `Idempotency-Key` com um UUID.
- [`version_unknown`](/erros/version_unknown.md) (HTTP 400): A `Kernos-Version` pedida não existe. Use uma das versões declaradas no cabeçalho, ou não o mande.

### HTTP 401 · Sem credencial válida

- [`api_key_expired`](/erros/api_key_expired.md) (HTTP 401): A chave venceu. Gire a chave no painel e use a nova.
- [`api_key_wrong_environment`](/erros/api_key_wrong_environment.md) (HTTP 401): A chave é do outro ambiente: `ksk_test_` vai em https://stag.kernos.com.br, `ksk_live_` em https://api.kernos.com.br.
- [`unauthenticated`](/erros/unauthenticated.md) (HTTP 401): Credencial ausente, inválida ou expirada.

### HTTP 403 · Sem permissão

- [`agent_token_out_of_scope`](/erros/agent_token_out_of_scope.md) (HTTP 403): O token de agente (`kat_`) só vale na porta MCP. Para a API, use o token de sessão ou uma chave de integração.
- [`api_key_action_not_granted`](/erros/api_key_action_not_granted.md) (HTTP 403): A chave não tem esta ação. Edite as ações da chave no painel.
- [`api_key_out_of_scope`](/erros/api_key_out_of_scope.md) (HTTP 403): A chave só vale nas rotas do contrato público.
- [`depot_out_of_reach`](/erros/depot_out_of_reach.md) (HTTP 403): Quem chama não alcança o CD deste objeto. Peça alcance ao CD ou use outra credencial.
- [`forbidden_role`](/erros/forbidden_role.md) (HTTP 403): O papel de quem chama nesta empresa não permite esta ação.
- [`module_not_subscribed`](/erros/module_not_subscribed.md) (HTTP 403): A empresa não assina o módulo desta rota.
- [`tenant_mismatch`](/erros/tenant_mismatch.md) (HTTP 403): A credencial não pertence à empresa do endereço chamado.
- [`tenant_unavailable`](/erros/tenant_unavailable.md) (HTTP 403): A empresa está indisponível no momento.

### HTTP 409 · Conflito

- [`idempotency_key_reused`](/erros/idempotency_key_reused.md) (HTTP 409): Esta `Idempotency-Key` já foi usada com outro corpo. Gere uma chave nova para uma escrita nova.

### HTTP 422 · Não processável

- [`invalid_grace`](/erros/invalid_grace.md) (HTTP 422): A carência da troca do segredo vai de 0 a 168 horas.
- [`invalid_status`](/erros/invalid_status.md) (HTTP 422): Situação inválida para este filtro ou campo: use um dos valores declarados.
- [`invalid_webhook_events`](/erros/invalid_webhook_events.md) (HTTP 422): Escolha ao menos um evento público para o webhook, com o nome do contrato.
- [`invalid_webhook_url`](/erros/invalid_webhook_url.md) (HTTP 422): O webhook é uma URL HTTPS pública, sem usuário e senha, de até 500 caracteres. Endereço da rede interna não é aceito.

### HTTP 429 · Muitos pedidos

- [`rate_limited`](/erros/rate_limited.md) (HTTP 429): Muitos pedidos por segundo com esta chave. Espere o `Retry-After`, em segundos, e tente de novo.

### HTTP 503 ·

- [`vault_unavailable`](/erros/vault_unavailable.md) (HTTP 503): O cofre do servidor está fechado agora e nada foi guardado. Tente de novo em alguns minutos.

#### curl

**Cadastra ou muda o webhook da empresa**

```sh
curl -X PUT "https://stag.kernos.com.br/v1/webhook-endpoint" \
  -H "Authorization: Bearer ksk_test_…" \
  -H "Kernos-Version: 2026-10-03" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://seu-sistema.com.br/kernos/webhook",
    "events": ["sales_order.finish_packing", "shipment.dispatch"]
  }'
```

#### Node

**Cadastra ou muda o webhook da empresa**

```js
import { Kernos } from '@kernosbr/kernos'

const kernos = new Kernos(process.env.KERNOS_API_KEY)
const result = await kernos.integrationWebhook.save({
  url: 'https://seu-sistema.com.br/kernos/webhook',
  events: ['sales_order.finish_packing', 'shipment.dispatch']
})
```

#### Python

**Cadastra ou muda o webhook da empresa**

```python
import os
from kernos import Kernos

kernos = Kernos(os.environ["KERNOS_API_KEY"])
result = kernos.integration_webhook.save({
  "url": "https://seu-sistema.com.br/kernos/webhook",
  "events": ["sales_order.finish_packing", "shipment.dispatch"]
})
```

#### PHP

**Cadastra ou muda o webhook da empresa**

```php
<?php
$kernos = new Kernos\Kernos(getenv('KERNOS_API_KEY'));
$result = $kernos->integrationWebhook->save([
  'url' => 'https://seu-sistema.com.br/kernos/webhook',
  'events' => ['sales_order.finish_packing', 'shipment.dispatch']
]);
```

#### Java

**Cadastra ou muda o webhook da empresa**

```java
Kernos kernos = new Kernos(System.getenv("KERNOS_API_KEY"));
JsonNode result = kernos.integrationWebhook().save(Map.of(
    "url", "https://seu-sistema.com.br/kernos/webhook",
    "events", List.of("sales_order.finish_packing", "shipment.dispatch")
));
```

#### C#

**Cadastra ou muda o webhook da empresa**

```csharp
var kernos = new KernosClient(Environment.GetEnvironmentVariable("KERNOS_API_KEY"));
var result = await kernos.IntegrationWebhook.SaveAsync(new {
  url = "https://seu-sistema.com.br/kernos/webhook",
  events = new[] {"sales_order.finish_packing", "shipment.dispatch"}
});
```

#### Go

**Cadastra ou muda o webhook da empresa**

```go
client, err := kernos.New(os.Getenv("KERNOS_API_KEY"))
result, err := client.IntegrationWebhook.Save(ctx, map[string]any{
  "url": "https://seu-sistema.com.br/kernos/webhook",
  "events": []any{"sales_order.finish_packing", "shipment.dispatch"},
})
```

#### Rust

**Cadastra ou muda o webhook da empresa**

```rust
let kernos = Kernos::new(&std::env::var("KERNOS_API_KEY")?)?;
let result = kernos.integration_webhook().save(&json!({
  "url": "https://seu-sistema.com.br/kernos/webhook",
  "events": ["sales_order.finish_packing", "shipment.dispatch"]
}), None)?;
```

**Resposta · 200 (a forma, sem dado real)**

```json
{
  "ok": true,
  "data": {
    "endpoint": {
      "created_at": "2026-10-03T12:00:00Z",
      "disabled_reason": "…",
      "events": [
        "…"
      ],
      "failing_since": "2026-10-03T12:00:00Z",
      "id": "3f1e0c8a-5b7d-4c21-9a3e-0b1c2d3e4f50",
      "status": "active",
      "updated_at": "2026-10-03T12:00:00Z",
      "url": "…"
    },
    "secret": "…"
  },
  "error": null,
  "error_code": null,
  "field": null,
  "trace_id": null
}
```

> **Ambiente** `ksk_test_` fala com `https://stag.kernos.com.br`; `ksk_live_`, com `https://api.kernos.com.br`. O prefixo da chave escolhe o endereço.
