Webhooks
Em vez de consultar a API repetidamente, receba os eventos do Atys no seu servidor no momento em que acontecem.
Introdução
Você registra uma assinatura (uma URL sua) e escolhe quais eventos quer receber.
Quando um desses eventos ocorre na sua empresa, o Atys envia um POST com
Content-Type: application/json para a sua URL.
- A assinatura pode valer para toda a empresa ou apenas para um canal.
- Cada requisição vem assinada com HMAC-SHA256, para você confirmar que veio do Atys.
- Entregas com falha são retentadas com espera progressiva.
- Toda tentativa fica registrada num log de entregas que você pode consultar.
2xx. Devolva 200 assim que receber e processe depois, de forma assíncrona:
o Atys aguarda no máximo 10 segundos antes de considerar a tentativa falha.
X-Atys-Delivery como chave de idempotência e ignore
identificadores repetidos.
1) Catálogo de Eventos
Estes são os eventos entregues hoje. Você também pode obter esta lista pela API, em
GET /api/channel-webhooks-available-events.
| Evento | Quando dispara |
|---|---|
|
Conversas e mensagens
escopo: canal
Nascem dentro de um canal. São os únicos afetados por
company_channel_ids. |
|
omnichat.message.received |
Mensagem recebida de um contato |
omnichat.message.sent |
Mensagem enviada por atendente, chatbot, workflow ou API |
omnichat.conversation.created |
Nova conversa criada |
omnichat.conversation.assigned |
Conversa atribuída a um atendente (ou transferida) |
omnichat.conversation.closed |
Conversa encerrada (resolvida ou cancelada) |
|
CRM — Negócios
escopo: empresa
Não pertencem a canal nenhum. Chegam em todas as assinaturas que os assinarem.
|
|
crm.business.created |
Negócio criado |
crm.business.updated |
Valor, responsável ou score do negócio alterado |
crm.business.stage_changed |
Negócio movido de etapa |
crm.business.won |
Negócio marcado como ganho |
crm.business.lost |
Negócio marcado como perdido |
crm.business.archived |
Negócio arquivado |
|
Contatos
escopo: empresa
Não pertencem a canal nenhum. Chegam em todas as assinaturas que os assinarem.
|
|
contact.import.completed |
Importação de contatos concluída |
|
Teste
escopo: —
Sintético: não é assinável e só sai quando você chama o endpoint de teste.
|
|
ping |
Disparado só por você, via POST /api/channel-webhooks/{id}/test |
company_channel_ids é um filtro, não um escopo. Ele restringe
apenas os eventos de Conversas e mensagens aos canais que você listar.
Os eventos de CRM e Contatos não pertencem a canal nenhum:
chegam sempre, tenha a assinatura canais listados ou não. Deixe company_channel_ids
vazio ou null para receber de todos os canais.
2) Formato da Requisição
Headers enviados pelo Atys
| Header | Exemplo | Descrição |
|---|---|---|
X-Atys-Event |
omnichat.message.received | Nome do evento |
X-Atys-Delivery |
d290f1ee-6c54-4b01-90e6-d701748f0851 | Identificador da entrega. Use como chave de idempotência — é o mesmo em todas as retentativas |
X-Atys-Timestamp |
1755436800 | Unix timestamp da tentativa. Faz parte do material assinado |
X-Atys-Signature |
sha256=9f86d081… | Assinatura HMAC-SHA256 do corpo (ver §3) |
X-Atys-Attempt |
1 | Número da tentativa (1 a 5) |
User-Agent |
Atys-Webhooks/1.0 | Identificação do agente |
Envelope do corpo
Todo evento tem a mesma estrutura externa; o que muda é o conteúdo de data.
{
"event": "crm.business.won",
"delivery_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"company_id": 42,
"occurred_at": "2026-08-17T13:20:00-03:00",
"description": "Negócio marcado como ganho",
"data": {
"...": "específico de cada evento (ver §4)"
}
}
3) Validar a Assinatura
O secret é devolvido uma única vez, na resposta da criação da
assinatura (e ao rotacioná-lo). Guarde-o com segurança: é com ele que você confirma que a
requisição veio do Atys e não foi alterada no caminho.
O material assinado é o timestamp e o corpo cru, unidos por um ponto:
assinatura = "sha256=" + HMAC_SHA256(secret, X-Atys-Timestamp + "." + corpo_cru)
hash_equals, crypto.timingSafeEqual,
hmac.compare_digest), nunca com ==.
Rejeite requisições cujo X-Atys-Timestamp esteja muito distante do horário atual
(5 minutos é uma tolerância comum) para impedir reenvio de uma requisição capturada.
Exemplos de Código
const crypto = require('crypto');
const express = require('express');
const app = express();
const SECRET = process.env.ATYS_WEBHOOK_SECRET;
// Importante: o corpo CRU é necessário para validar a assinatura.
app.post('/atys/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const assinatura = req.get('X-Atys-Signature') || '';
const timestamp = req.get('X-Atys-Timestamp') || '';
const corpoCru = req.body.toString('utf8');
// Rejeita requisições antigas (proteção contra reenvio)
const idadeEmSegundos = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!timestamp || idadeEmSegundos > 300) {
return res.status(401).send('timestamp fora da janela');
}
const esperada = 'sha256=' + crypto
.createHmac('sha256', SECRET)
.update(timestamp + '.' + corpoCru)
.digest('hex');
const a = Buffer.from(assinatura);
const b = Buffer.from(esperada);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).send('assinatura inválida');
}
const evento = JSON.parse(corpoCru);
console.log(evento.event, evento.delivery_id);
// Responda já e processe depois.
res.sendStatus(200);
});
app.listen(3000);
import hashlib
import hmac
import json
import os
import time
from flask import Flask, request
app = Flask(__name__)
SECRET = os.environ['ATYS_WEBHOOK_SECRET'].encode()
@app.post('/atys/webhook')
def atys_webhook():
assinatura = request.headers.get('X-Atys-Signature', '')
timestamp = request.headers.get('X-Atys-Timestamp', '')
corpo_cru = request.get_data() # bytes, sem reprocessar
if not timestamp or abs(time.time() - int(timestamp)) > 300:
return 'timestamp fora da janela', 401
esperada = 'sha256=' + hmac.new(
SECRET,
f'{timestamp}.'.encode() + corpo_cru,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(assinatura, esperada):
return 'assinatura inválida', 401
evento = json.loads(corpo_cru)
print(evento['event'], evento['delivery_id'])
return '', 200
<?php
$secret = getenv('ATYS_WEBHOOK_SECRET');
$assinatura = $_SERVER['HTTP_X_ATYS_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_ATYS_TIMESTAMP'] ?? '';
$corpoCru = file_get_contents('php://input');
if (! $timestamp || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit('timestamp fora da janela');
}
$esperada = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $corpoCru, $secret);
if (! hash_equals($esperada, $assinatura)) {
http_response_code(401);
exit('assinatura inválida');
}
$evento = json_decode($corpoCru, true);
error_log($evento['event'] . ' ' . $evento['delivery_id']);
http_response_code(200);
<?php
// routes/web.php — lembre-se de isentar a rota do CSRF
Route::post('/atys/webhook', function (\Illuminate\Http\Request $request) {
$secret = config('services.atys.webhook_secret');
$assinatura = $request->header('X-Atys-Signature', '');
$timestamp = $request->header('X-Atys-Timestamp', '');
$corpoCru = $request->getContent();
if (! $timestamp || abs(time() - (int) $timestamp) > 300) {
abort(401, 'timestamp fora da janela');
}
$esperada = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $corpoCru, $secret);
if (! hash_equals($esperada, $assinatura)) {
abort(401, 'assinatura inválida');
}
$evento = $request->json()->all();
// Idempotência: ignore delivery_id já processado.
\App\Jobs\ProcessarEventoAtys::dispatch($evento);
return response()->noContent();
});
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"math"
"net/http"
"os"
"strconv"
"time"
)
var secret = []byte(os.Getenv("ATYS_WEBHOOK_SECRET"))
func handler(w http.ResponseWriter, r *http.Request) {
corpoCru, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "corpo ilegível", http.StatusBadRequest)
return
}
timestamp := r.Header.Get("X-Atys-Timestamp")
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil || math.Abs(float64(time.Now().Unix()-ts)) > 300 {
http.Error(w, "timestamp fora da janela", http.StatusUnauthorized)
return
}
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(timestamp + "."))
mac.Write(corpoCru)
esperada := "sha256=" + hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(esperada), []byte(r.Header.Get("X-Atys-Signature"))) {
http.Error(w, "assinatura inválida", http.StatusUnauthorized)
return
}
var evento struct {
Event string `json:"event"`
DeliveryID string `json:"delivery_id"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(corpoCru, &evento); err != nil {
http.Error(w, "json inválido", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
}
func main() {
http.HandleFunc("/atys/webhook", handler)
http.ListenAndServe(":3000", nil)
}
# Útil para conferir sua implementação: gere a assinatura do mesmo material
# e compare com o header que o Atys enviou.
SECRET='whsec_seu_secret_aqui'
TIMESTAMP='1755436800'
BODY='{"event":"ping","delivery_id":"abc","company_id":42,"data":{}}'
printf '%s.%s' "$TIMESTAMP" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET" -hex \
| sed 's/^.*= /sha256=/'
4) Payload por Evento
Exemplos do conteúdo de data. Campos podem ser adicionados no futuro — ignore os que não usa.
omnichat.message.received e omnichat.message.sent
{
"id": 998877,
"uuid": "3f1c9a2e-7c1b-4a90-9a1d-2b7f5c8e1234",
"external_id": "3EB0C512B5A1B9D7F8A1",
"text": "Bom dia, gostaria de um orçamento",
"message_type_id": 1,
"message_source_id": 2,
"status_id": 12,
"edited": false,
"is_forwarded": false,
"revoked_at": null,
"company_channel_id": 15,
"company_channel_key": "5511999990000",
"contact_channel_id": 4321,
"contact_channel_key": "5511988887777",
"replied_message_id": null,
"user_id": null,
"platform_at": "2026-08-17T13:19:58-03:00",
"received_at": "2026-08-17T13:19:59-03:00",
"created_at": "2026-08-17T13:20:00-03:00",
"conversation": {
"id": 4321,
"name": "Maria Souza",
"key": "5511988887777",
"is_group": false
}
}
message_source_id distingue a origem: 2 = contato (por isso o evento
é received). Qualquer outro valor — atendente, chatbot, API, workflow — gera
omnichat.message.sent.
omnichat.conversation.*
{
"id": 4321,
"name": "Maria Souza",
"key": "5511988887777",
"phone": "5511988887777",
"is_group": false,
"status_id": 67,
"company_channel_id": 15,
"channel_type_id": 2,
"channel_type": "Whatsapp",
"sector": { "id": 3, "name": "Comercial" },
"assigned_user": { "id": 88, "name": "João Lima", "email": "joao@empresa.com" },
"origin_id": 1,
"contact": { "id": 777, "name": "Maria Souza", "email": "maria@exemplo.com" },
"unread_count": 0,
"last_message_at": "2026-08-17T13:20:00-03:00",
"created_at": "2026-08-17T10:02:11-03:00",
"change_context": {
"previous_user_id": null,
"assigned_user_id": 88
}
}
change_context aparece em assigned (com previous_user_id e
assigned_user_id) e em closed (com previous_status_id e
status_id). Em created ele não vem.
crm.business.*
{
"id": 5150,
"title": "Implantação — Loja Central",
"description": "Contato veio da campanha de agosto",
"score": 78.5,
"archived": false,
"sold_at": "2026-08-17T13:15:00-03:00",
"canceled_at": null,
"priority_id": 2,
"source": { "id": 4, "name": "Indicação" },
"pipeline": { "id": 9, "name": "Vendas Brasil" },
"stage": { "id": 41, "name": "Fechado" },
"created_at": "2026-07-30T09:12:00-03:00",
"change_context": {
"sold_at": "2026-08-17T13:15:00-03:00"
}
}
O conteúdo de change_context depende do evento:
stage_changed traz from_stage_id, to_stage_id,
pipeline_id e changed_by_user_id;
updated traz change (value, owner ou
score) e os valores anterior/novo;
won, lost e archived traçam a respectiva data.
contact.import.completed
{
"import_id": "imp_9f3c1a7b",
"total_contacts": 1200,
"imported_contacts": 1187,
"skipped_contacts": 11,
"failed_contacts": 2,
"started_by_user_id": 88,
"completed_at": "2026-08-17T13:20:00-03:00"
}
5) Criar uma Assinatura
Registra uma URL para receber eventos. A resposta traz o secret —
é a única vez que ele aparece.
https://api.atys.pro/api/channel-webhooks
Criar assinatura de webhook
Headers Obrigatórios
| Header | Valor | Descrição |
|---|---|---|
Authorization | Bearer YOUR_ACCESS_TOKEN | Token de autenticação |
Content-Type | application/json | Tipo do conteúdo enviado |
Accept | application/json | Tipo de resposta esperada |
Request Body
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
name | string | ✅ Sim | Nome de identificação (máx. 150 caracteres) |
url | string | ✅ Sim | URL que receberá o POST. Use HTTPS |
events | array | Não | Eventos assinados. Omitir = todos. Nomes inválidos são descartados |
company_channel_ids | array | Não | Restringe os eventos de Conversas e mensagens a estes canais.
Omitir ou null = todos os canais.
Não afeta os eventos de CRM e Contatos, que chegam sempre |
enabled | boolean | Não | Padrão true |
Exemplos de Código
const response = await fetch('https://api.atys.pro/api/channel-webhooks', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
name: 'Integração ERP',
url: 'https://meuservidor.com/atys/webhook',
events: ['omnichat.message.received', 'crm.business.won']
})
});
const data = await response.json();
// Guarde o secret agora: ele não é exibido de novo.
console.log(data.secret);
import requests
response = requests.post(
'https://api.atys.pro/api/channel-webhooks',
headers={
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
json={
'name': 'Integração ERP',
'url': 'https://meuservidor.com/atys/webhook',
'events': ['omnichat.message.received', 'crm.business.won'],
},
)
data = response.json()
# Guarde o secret agora: ele não é exibido de novo.
print(data['secret'])
<?php
$ch = curl_init('https://api.atys.pro/api/channel-webhooks');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_ACCESS_TOKEN',
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Integração ERP',
'url' => 'https://meuservidor.com/atys/webhook',
'events' => ['omnichat.message.received', 'crm.business.won'],
]),
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
// Guarde o secret agora: ele não é exibido de novo.
echo $data['secret'];
<?php
use GuzzleHttp\Client;
$client = new Client();
$response = $client->post('https://api.atys.pro/api/channel-webhooks', [
'headers' => [
'Authorization' => 'Bearer YOUR_ACCESS_TOKEN',
'Accept' => 'application/json',
],
'json' => [
'name' => 'Integração ERP',
'url' => 'https://meuservidor.com/atys/webhook',
'events' => ['omnichat.message.received', 'crm.business.won'],
],
]);
$data = json_decode($response->getBody()->getContents(), true);
// Guarde o secret agora: ele não é exibido de novo.
echo $data['secret'];
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
corpo, _ := json.Marshal(map[string]any{
"name": "Integração ERP",
"url": "https://meuservidor.com/atys/webhook",
"events": []string{"omnichat.message.received", "crm.business.won"},
})
req, _ := http.NewRequest(
"POST",
"https://api.atys.pro/api/channel-webhooks",
bytes.NewBuffer(corpo),
)
req.Header.Set("Authorization", "Bearer YOUR_ACCESS_TOKEN")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var data struct {
Secret string `json:"secret"`
}
json.NewDecoder(resp.Body).Decode(&data)
// Guarde o secret agora: ele não é exibido de novo.
fmt.Println(data.Secret)
}
curl -X POST https://api.atys.pro/api/channel-webhooks \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "Integração ERP",
"url": "https://meuservidor.com/atys/webhook",
"events": ["omnichat.message.received", "crm.business.won"]
}'
Resposta de Sucesso (201)
{
"success": true,
"data": {
"id": 12,
"company_id": 42,
"company_channel_ids": null,
"name": "Integração ERP",
"url": "https://meuservidor.com/atys/webhook",
"events": ["omnichat.message.received", "crm.business.won"],
"enabled": true,
"last_success_at": null,
"last_failure_at": null,
"failure_count": 0,
"disabled_reason": null,
"created_at": "2026-08-17 13:20:00",
"updated_at": "2026-08-17 13:20:00"
},
"secret": "whsec_4f8a1c9e7b2d5a3f6c8e0b1d4a7f2c9e5b8d1a3f6c0e2b4d",
"message": "Guarde o secret: ele é exibido apenas nesta resposta e é necessário para validar a assinatura X-Atys-Signature."
}
6) Listar Assinaturas
Lista paginada das assinaturas da sua empresa. O secret nunca é retornado aqui.
https://api.atys.pro/api/channel-webhooks
Listar assinaturas de webhook
Query Parameters
| Parâmetro | Tipo | Descrição | Exemplo |
|---|---|---|---|
filter | string | Busca textual em name e url | ERP |
enabled | boolean | Filtra por ativas/inativas | true |
company_channel_id | integer | Assinaturas que recebem eventos desse canal — inclui as que não filtram canal nenhum | 15 |
orderBy | string | Campo de ordenação (padrão id) | name |
orderMode | string | ASC ou DESC (padrão ASC) | DESC |
perPage | integer | Itens por página (padrão 100) | 20 |
curl -X GET 'https://api.atys.pro/api/channel-webhooks?enabled=true&perPage=20' \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json"
Catálogo de eventos disponíveis
https://api.atys.pro/api/channel-webhooks-available-events
Listar os eventos que podem ser assinados
{
"success": true,
"data": [
{ "event": "omnichat.message.received", "description": "Nova mensagem recebida de um contato" },
{ "event": "omnichat.message.sent", "description": "Mensagem enviada por um atendente, bot ou pela API" }
]
}
7) Atualizar Assinatura
Altera nome, URL, eventos, canal ou o estado da assinatura. Envie apenas os campos que quer mudar.
https://api.atys.pro/api/channel-webhooks/{id}
Atualizar assinatura de webhook
enabled: true)
zera o contador de falhas e limpa o disabled_reason. Sem isso, a
primeira falha seguinte a desligaria de novo imediatamente.
curl -X PUT https://api.atys.pro/api/channel-webhooks/12 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"events": ["omnichat.message.received", "omnichat.conversation.closed"],
"enabled": true
}'
8) Testar Assinatura
Dispara um evento sintético ping para a URL da assinatura. Use ao configurar,
para confirmar que o endpoint responde e que sua validação de assinatura funciona.
https://api.atys.pro/api/channel-webhooks/{id}/test
Enviar evento de teste
curl -X POST https://api.atys.pro/api/channel-webhooks/12/test \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json"
Resposta de Sucesso (200)
{
"success": true,
"queued": 1,
"message": "Evento de teste enfileirado. Consulte as entregas para ver o resultado."
}
A entrega é assíncrona: consulte o log (§9) para ver o código de resposta do seu servidor.
9) Log de Entregas
Histórico de tentativas da assinatura, da mais recente para a mais antiga. É a ferramenta para depurar sozinho por que um evento não chegou.
https://api.atys.pro/api/channel-webhooks/{id}/deliveries
Consultar log de entregas
Query Parameters
| Parâmetro | Tipo | Descrição | Exemplo |
|---|---|---|---|
event | string | Filtra por nome do evento | crm.business.won |
succeeded | boolean | false mostra só as falhas | false |
perPage | integer | Itens por página (padrão 50) | 20 |
Resposta de Sucesso (200)
{
"current_page": 1,
"per_page": 50,
"total": 2,
"data": [
{
"id": 90211,
"delivery_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"event": "crm.business.won",
"attempt": 1,
"status_code": 200,
"succeeded": true,
"duration_ms": 143,
"error": null,
"delivered_at": "2026-08-17 13:20:01",
"next_retry_at": null,
"created_at": "2026-08-17 13:20:00"
},
{
"id": 90210,
"delivery_id": "b81f2c04-1a77-4f0e-8c3d-99aa1b2c3d4e",
"event": "omnichat.message.received",
"attempt": 3,
"status_code": 500,
"succeeded": false,
"duration_ms": 2011,
"error": "HTTP 500",
"delivered_at": null,
"next_retry_at": "2026-08-17 13:25:00",
"created_at": "2026-08-17 13:18:42"
}
]
}
10) Rotacionar o Secret
Gera um novo secret e invalida o anterior imediatamente. Faça isso se
suspeitar que o secret foi exposto — e atualize o seu servidor antes do próximo evento.
https://api.atys.pro/api/channel-webhooks/{id}/rotate-secret
Gerar novo secret de assinatura
curl -X POST https://api.atys.pro/api/channel-webhooks/12/rotate-secret \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json"
Resposta de Sucesso (200)
{
"success": true,
"data": { "id": 12, "name": "Integração ERP", "...": "..." },
"secret": "whsec_1b7d3f5a9c2e8b4d6f0a3c7e1b5d9f2a4c6e8b0d3f5a7c9e",
"message": "Secret rotacionado. O anterior deixou de ser válido imediatamente."
}
11) Remover Assinatura
Remove a assinatura. Os eventos deixam de ser entregues na hora.
https://api.atys.pro/api/channel-webhooks/{id}
Remover assinatura de webhook
curl -X DELETE https://api.atys.pro/api/channel-webhooks/12 \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json"
12) Retentativas e Desativação
Uma entrega é considerada bem-sucedida com qualquer resposta 2xx. Fora disso, o Atys
retenta até 5 vezes com espera progressiva:
| Tentativa | Espera desde a anterior |
|---|---|
| 1 | imediata |
| 2 | 10 segundos |
| 3 | 1 minuto |
| 4 | 5 minutos |
| 5 | 15 minutos |
408 e 429): se o
seu servidor responde 404 ou 401, o problema é de configuração e
insistir não resolveria. Corrija e use o endpoint de teste (§8).
Desativação automática: após 20 eventos consecutivos que
esgotaram todas as tentativas, a assinatura é desativada — enabled vira
false e disabled_reason explica o motivo. Uma entrega bem-sucedida
zera o contador. Para reativar, atualize a assinatura com enabled: true (§7).
13) Erros Comuns
Códigos devolvidos pela API de gestão de assinaturas (não pelo seu endpoint):
| Código | Significado | Causa provável |
|---|---|---|
401 | Não autenticado | Token ausente, inválido ou expirado. Ver Autenticação |
403 | Sem permissão | O usuário não tem a permissão necessária (channel_webhooks_view, _create, _update ou _delete) |
422 | Validação falhou | name ausente, ou url ausente / mal formada |
429 | Limite de requisições | Excedeu o rate limit da API. Respeite o header Retry-After |
509 | Erro de negócio | Registro não encontrado, pertence a outra empresa, ou teste disparado numa assinatura desativada |
509 para erros de negócio e validação de regra, não os códigos
400/422 convencionais. Trate 509 como erro esperado e leia
a mensagem do corpo.
Esta página foi útil?