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.
💡 Responda rápido. Considere a entrega bem-sucedida qualquer resposta 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.
Entrega ao menos uma vez. Uma retentativa pode reenviar um evento que você já processou. Use o header 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.

Envelope — comum a todos os eventos
{
  "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:

Algoritmo da assinatura
assinatura = "sha256=" + HMAC_SHA256(secret, X-Atys-Timestamp + "." + corpo_cru)
Use o corpo cru da requisição, exatamente como recebido — não o JSON re-serializado. Reordenar chaves ou mudar espaçamento altera o hash. Compare sempre com uma função de tempo constante (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

Node.js / Express
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);
Python / Flask
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 puro
<?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 (Laravel)
<?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();
});
Go
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)
}
cURL — reproduzir a assinatura localmente
# Ú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

data — omnichat.message.received / .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.*

data — 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.*

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

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.

POST

https://api.atys.pro/api/channel-webhooks

Criar assinatura de webhook

Headers Obrigatórios

HeaderValorDescrição
AuthorizationBearer YOUR_ACCESS_TOKENToken de autenticação
Content-Typeapplication/jsonTipo do conteúdo enviado
Acceptapplication/jsonTipo de resposta esperada

Request Body

ParâmetroTipoObrigatórioDescrição
namestring✅ Sim Nome de identificação (máx. 150 caracteres)
urlstring✅ Sim URL que receberá o POST. Use HTTPS
eventsarrayNão Eventos assinados. Omitir = todos. Nomes inválidos são descartados
company_channel_idsarrayNã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
enabledbooleanNão Padrão true

Exemplos de Código

JavaScript (Fetch API)
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);
Python (requests)
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 (cURL)
<?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 (Guzzle)
<?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'];
Go
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
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)

201 Created — assinatura criada
{
  "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.

GET

https://api.atys.pro/api/channel-webhooks

Listar assinaturas de webhook

Query Parameters

ParâmetroTipoDescriçãoExemplo
filterstringBusca textual em name e urlERP
enabledbooleanFiltra por ativas/inativastrue
company_channel_idintegerAssinaturas que recebem eventos desse canal — inclui as que não filtram canal nenhum15
orderBystringCampo de ordenação (padrão id)name
orderModestringASC ou DESC (padrão ASC)DESC
perPageintegerItens por página (padrão 100)20
cURL
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

GET

https://api.atys.pro/api/channel-webhooks-available-events

Listar os eventos que podem ser assinados

200 OK — eventos disponíveis
{
  "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.

PUT

https://api.atys.pro/api/channel-webhooks/{id}

Atualizar assinatura de webhook

💡 Reativar uma assinatura que foi desativada automaticamente (enabled: true) zera o contador de falhas e limpa o disabled_reason. Sem isso, a primeira falha seguinte a desligaria de novo imediatamente.
cURL
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.

POST

https://api.atys.pro/api/channel-webhooks/{id}/test

Enviar evento de teste

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

200 OK — teste enfileirado
{
  "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.

GET

https://api.atys.pro/api/channel-webhooks/{id}/deliveries

Consultar log de entregas

Query Parameters

ParâmetroTipoDescriçãoExemplo
eventstringFiltra por nome do eventocrm.business.won
succeededbooleanfalse mostra só as falhasfalse
perPageintegerItens por página (padrão 50)20

Resposta de Sucesso (200)

200 OK — log de entregas
{
  "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"
    }
  ]
}
💡 O log tem retenção: entregas bem-sucedidas são mantidas por 7 dias e as com falha por 30 dias. Se precisar de histórico maior, guarde os eventos do seu lado.

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.

POST

https://api.atys.pro/api/channel-webhooks/{id}/rotate-secret

Gerar novo secret de assinatura

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

200 OK — secret rotacionado
{
  "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.

DELETE

https://api.atys.pro/api/channel-webhooks/{id}

Remover assinatura de webhook

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

TentativaEspera desde a anterior
1imediata
210 segundos
31 minuto
45 minutos
515 minutos
Erros 4xx não são retentados (exceto 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ódigoSignificadoCausa provável
401Não autenticado Token ausente, inválido ou expirado. Ver Autenticação
403Sem permissão O usuário não tem a permissão necessária (channel_webhooks_view, _create, _update ou _delete)
422Validação falhou name ausente, ou url ausente / mal formada
429Limite de requisições Excedeu o rate limit da API. Respeite o header Retry-After
509Erro de negócio Registro não encontrado, pertence a outra empresa, ou teste disparado numa assinatura desativada
💡 O Atys usa 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?