Pular para o conteúdo principal

Receber eventos por webhook

O canal Webhook deixa outro sistema — a sua loja virtual, o sistema de cobrança, a central de chamados — avisar a plataforma quando algo acontece: uma compra feita, um pagamento aprovado, um chamado aberto. O sistema de origem faz uma chamada HTTP para um endereço seu; a plataforma confere quem chamou, recebe o aviso e, se você quiser, aciona um agente para cuidar dele — e pode devolver a resposta do agente ao seu sistema.

Aqui ninguém "conversa": quem fala é um sistema. Cada chamada é um aviso (também chamado de evento), e a tela usa a palavra aviso.

Use o webhook quando você quiser:

  • ver o que acontece em outros sistemas, aviso por aviso, sem escrever código do seu lado;
  • deixar um agente agir sozinho a cada aviso — por exemplo, conferir um pedido ou preparar uma resposta, com as ferramentas liberadas ao público;
  • receber a resposta do agente de volta no sistema de origem, para mostrá-la ao cliente ou gravá-la no pedido;
  • testar a integração com segurança antes de ligar o agente.

Como funciona​

A plataforma só responde 200 com accepted depois de guardar o aviso com segurança. O agente (ou o registro na tela) cuida dele em seguida, sem fazer o sistema de origem esperar. No modo acionar, a plataforma pode também enviar a resposta do agente ao seu sistema: veja Receber a resposta do agente.

Criar um webhook​

Só administradores da organização criam e alteram webhooks. No celular, a tela é só de leitura: use o computador.

  1. No painel, abra Configurações → Canais e vá até a seção Webhook.
  2. Clique em Novo webhook e preencha:
    • Nome — para você reconhecer na lista, por exemplo Pedidos da loja;
    • Agente — quem recebe os avisos deste webhook;
    • Autenticação — como o sistema de origem prova quem é (veja abaixo);
    • Instrução (opcional) — o que o agente deve fazer com cada aviso;
    • Quem pode enviar (opcional) — uma lista de endereços de IP;
    • Opções avançadas (opcional) — como ler o corpo de cada aviso (veja abaixo).
  3. Clique em Criar webhook.
  4. Aparece o cartão Webhook criado, com o Endereço (URL) do webhook e, conforme a autenticação escolhida, o Segredo da assinatura, o Token ou a Senha. Copie tudo e clique em Já copiei. (Se você colou o segredo da assinatura, ele não é mostrado de volta: você já o tem.)
  5. Cole o endereço no sistema de origem, como o destino dos avisos.
Copie na hora

A URL e os segredos gerados aparecem uma única vez: depois disso, não há como vê-los de novo. Se perder, use Trocar a URL (ou troque a autenticação) no detalhe do webhook.

Todo webhook nasce no modo só registrar: os avisos chegam e aparecem na Atividade (um resumo dos mais recentes, não um histórico completo), e o agente não age sozinho. Quando você estiver satisfeito com o que chega, passe para acionar o agente.

A URL e a chave​

A URL do webhook termina em /v1/hooks/ seguido da chave:

https://api.lab019.ai/gateway/v1/hooks/SUA_CHAVE_AQUI

A tela mostra a URL inteira ao criar o webhook: copie e cole essa no sistema de origem. Nos exemplos desta página, SUA_CHAVE_AQUI é de mentira — troque pela chave da sua URL.

  • O sistema de origem sempre envia com o método POST.
  • A última parte do endereço é a chave: ela identifica o webhook e, sozinha, já é uma credencial. É longa e impossível de adivinhar, mas quem tem a URL consegue enviar avisos — trate-a como uma senha e não a compartilhe.
  • Trocar a URL (no detalhe do webhook) gera uma chave nova e derruba a anterior na hora. A autenticação escolhida continua a mesma. O sistema de origem precisa receber o endereço novo; até lá, os avisos deixam de chegar.
  • Apagar o webhook desliga a URL de vez.

Autenticação​

A chave da URL sempre vale. A Autenticação acrescenta uma segunda conferência, e você escolhe entre quatro tipos:

Na telaIdentificadorO sistema de origem precisa…
Só a chave da URLurl_keyEnviar para a URL. Mais nada.
AssinaturahmacAssinar cada aviso com um segredo combinado e mandar a assinatura num cabeçalho. É a opção mais segura, quando o sistema de origem sabe assinar.
Token no cabeçalhoheaderMandar um token num cabeçalho de cada chamada.
Usuário e senhabasicMandar usuário e senha no cabeçalho Authorization (Basic).

Os exemplos usam chave e segredos de mentira (SUA_CHAVE_AQUI, segredo-de-exemplo): troque pelos seus.

Só a chave da URL​

O mais simples: quem tem a URL envia.

Só a chave da URL
curl -X POST "https://api.lab019.ai/gateway/v1/hooks/SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{"type": "pedido.criado", "id": "evt_123", "data": {"pedido": "1001"}}'

A resposta é 200:

{"status": "accepted", "eventId": "3f2b8c1a-5d4e-4a7b-9c10-2e6d8f0a1b34"}

Assinatura (HMAC)​

O sistema de origem calcula uma assinatura do corpo do aviso com um segredo combinado e a manda num cabeçalho. A plataforma refaz a conta e só aceita se bater. Na tela você informa:

CampoO que é
Cabeçalho da assinaturaO nome do cabeçalho que leva a assinatura, por exemplo X-Hub-Signature-256.
Algoritmo da assinaturaSHA-256 (o mais comum), SHA-1 ou SHA-512.
Formato da assinaturaHexadecimal (o mais comum) ou Base64.
Prefixo antes da assinatura (opcional)O que o sistema de origem escreve antes da assinatura, como sha256=.
Segredo que o sistema de origem gerou (opcional)Se o sistema de origem já tem um segredo, cole aqui. Em branco, a plataforma gera um e mostra uma única vez.

Como a assinatura é conferida:

  • A conta é o HMAC do corpo exatamente como foi enviado, byte a byte. Assine os mesmos bytes que você manda: se o seu código monta o JSON, assine o texto final, e não o objeto de novo serializado (espaços e ordem dos campos mudam a assinatura).
  • O nome do cabeçalho não diferencia maiúsculas de minúsculas. Em hexadecimal, letras maiúsculas ou minúsculas servem; em Base64, use o Base64 comum, com = no fim (não a variante para URLs, com - e _).
  • Se você configurou um prefixo, o valor do cabeçalho precisa começar exatamente com ele (sha256= seguido da assinatura).
  • Corpo alterado em um único byte, assinatura de outro segredo ou cabeçalho ausente resultam em 401.

Os exemplos abaixo supõem: cabeçalho X-Hub-Signature-256, algoritmo SHA-256, formato hexadecimal, prefixo sha256= e segredo segredo-de-exemplo.

Python​

enviar.py
import hashlib
import hmac
import urllib.request

SEGREDO = "segredo-de-exemplo"
URL = "https://api.lab019.ai/gateway/v1/hooks/SUA_CHAVE_AQUI"

# O texto exato que será enviado: a assinatura é calculada sobre estes bytes.
corpo = '{"type": "pedido.criado", "id": "evt_123", "data": {"pedido": "1001"}}'.encode("utf-8")

assinatura = hmac.new(SEGREDO.encode("utf-8"), corpo, hashlib.sha256).hexdigest()

pedido = urllib.request.Request(
URL,
data=corpo,
method="POST",
headers={
"Content-Type": "application/json",
"X-Hub-Signature-256": f"sha256={assinatura}",
},
)
with urllib.request.urlopen(pedido) as resposta:
print(resposta.status, resposta.read().decode("utf-8"))

Node.js​

Salve como enviar.mjs e rode com node enviar.mjs (Node 18 ou mais novo, sem instalar nada):

enviar.mjs
import crypto from "node:crypto";

const SEGREDO = "segredo-de-exemplo";
const URL = "https://api.lab019.ai/gateway/v1/hooks/SUA_CHAVE_AQUI";

// O texto exato que será enviado: a assinatura é calculada sobre estes bytes.
const corpo = '{"type": "pedido.criado", "id": "evt_123", "data": {"pedido": "1001"}}';

const assinatura = crypto.createHmac("sha256", SEGREDO).update(corpo, "utf8").digest("hex");

const resposta = await fetch(URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Hub-Signature-256": `sha256=${assinatura}`,
},
body: corpo,
});
console.log(resposta.status, await resposta.text());

Linha de comando​

Assinatura (HMAC) com curl
CORPO='{"type": "pedido.criado", "id": "evt_123", "data": {"pedido": "1001"}}'
ASSINATURA=$(printf '%s' "$CORPO" | openssl dgst -sha256 -hmac "segredo-de-exemplo" | sed 's/^.* //')

curl -X POST "https://api.lab019.ai/gateway/v1/hooks/SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: sha256=$ASSINATURA" \
-d "$CORPO"

Se o formato escolhido for Base64, a assinatura muda de forma, e só ela:

Assinatura em Base64 (Python)
import base64

assinatura = base64.b64encode(
hmac.new(SEGREDO.encode("utf-8"), corpo, hashlib.sha256).digest()
).decode("ascii")
Assinatura em Base64 (Node.js)
const assinatura = crypto.createHmac("sha256", SEGREDO).update(corpo, "utf8").digest("base64");

Para SHA-1 ou SHA-512, troque o algoritmo nos dois lados (hashlib.sha1 ou hashlib.sha512 no Python; "sha1" ou "sha512" no Node.js).

Token no cabeçalho​

O sistema de origem manda um token (uma senha) num cabeçalho de cada chamada. Na tela você informa:

CampoO que é
Nome do cabeçalho do tokenO cabeçalho que leva o token, por exemplo Authorization ou X-Token.
Começo do valor (opcional)O que vem antes do token, como Bearer (com o espaço no final).

A plataforma gera o token e mostra uma única vez ao criar o webhook. Guarde-o no cofre de segredos do sistema de origem.

Token no cabeçalho
curl -X POST "https://api.lab019.ai/gateway/v1/hooks/SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer SEU_TOKEN_AQUI" \
-d '{"type": "pedido.criado", "id": "evt_123", "data": {"pedido": "1001"}}'

O exemplo supõe o cabeçalho Authorization e o começo do valor Bearer .

Usuário e senha​

O sistema de origem manda um usuário e uma senha no cabeçalho Authorization, no formato Basic. Na tela você informa só o Usuário; a plataforma gera a senha e a mostra uma única vez.

Usuário e senha
curl -X POST "https://api.lab019.ai/gateway/v1/hooks/SUA_CHAVE_AQUI" \
-u "usuario-de-exemplo:SUA_SENHA_AQUI" \
-H "Content-Type: application/json" \
-d '{"type": "pedido.criado", "id": "evt_123", "data": {"pedido": "1001"}}'

Se o usuário ou a senha estiverem errados, a resposta é 401 com o cabeçalho WWW-Authenticate: Basic realm="webhook".

Trocar a autenticação​

  • Trocar o tipo de autenticação gera um segredo novo (uma senha ou um token), mostrado uma única vez depois de salvar. O segredo anterior deixa de valer na hora.
  • Passar para Só a chave da URL apaga o segredo guardado.
  • Editar o webhook sem mudar o tipo mantém o segredo atual.

Quem pode enviar: filtro de IP​

Em Quem pode enviar (opcional) você lista endereços de IP, um por linha ou separados por vírgula, como 203.0.113.7 ou 198.51.100.0/24. Em branco, qualquer endereço pode enviar. Com a lista preenchida, O que a lista faz define como ela vale:

  • Só estes endereços podem enviar — qualquer outro endereço recebe 403;
  • Estes endereços são bloqueados — os da lista recebem 403, os demais passam.

O filtro de IP vale junto com a autenticação, não no lugar dela.

O que o sistema de origem envia​

  • Método POST para a URL do webhook.
  • Cabeçalho Content-Type: application/json — ou application/x-www-form-urlencoded, se o webhook estiver configurado como Formulário (veja Tipo de conteúdo abaixo).
  • Corpo em UTF-8, sem compressão. No formato JSON, a raiz do corpo precisa ser um objeto ({ ... }).
  • Tamanho de até 256 KiB por padrão. O limite é configurável em Tamanho máximo do aviso (bytes), de 1024 a 1048576 (1 MiB). Um corpo maior recebe 413.

Como a plataforma lê cada aviso​

Tudo isto fica em Opções avançadas e é opcional. Deixando tudo em branco, o corpo inteiro vai para o agente e cada aviso é aceito.

Campo na telaO que faz
Tipo de conteúdoComo o sistema de origem manda o corpo: JSON (o padrão; aceita application/json e tipos terminados em +json) ou Formulário (form-urlencoded). Um Content-Type diferente do configurado recebe 415. Em formulário, um campo repetido vira uma lista.
Onde está o tipo do avisoDe onde a plataforma lê o tipo do aviso, por exemplo body:type ou header:X-Event.
Onde está o id da entregaDe onde ela lê o identificador único do aviso. Serve para não processar o mesmo aviso duas vezes.
Onde está a chave de correlaçãoAvisos com a mesma chave caem na mesma conversa do agente, por exemplo body:data.customer.id. Em branco, cada aviso abre uma conversa nova.
Tipos de aviso aceitosUma lista (um por linha ou separados por vírgula), por exemplo pedido.criado. Em branco, aceita todos. Para usar a lista, preencha também Onde está o tipo do aviso: com esse campo em branco, a plataforma não reconhece o tipo de nenhum aviso e todos voltam 200 com ignored. Um aviso de outro tipo — ou sem tipo reconhecível — é ignorado: a resposta é 200 com ignored e nada é registrado. O Schema é conferido antes do tipo: um aviso de outro tipo que não cumpra o schema recebe 422, e não ignored.
Campos enviados ao agenteOs caminhos que seguem adiante, sem body:, por exemplo data.order. Em branco, vai o corpo inteiro (menos os descartados).
Campos descartadosCaminhos removidos antes de qualquer gravação: nem o agente, nem a tela, nem os avisos que a plataforma guarda chegam a ver. Use o caminho completo, por exemplo data.customer.cpf.
Schema (opcional)Um JSON Schema que o corpo precisa cumprir. Se não cumprir, a resposta é 422. Aceita JSON Schema, menos as palavras pattern, patternProperties e $ref que aponte para fora do próprio schema. A palavra format não é verificada, e uma conferência que passe de 2 segundos também recebe 422.
Tamanho máximo do aviso (bytes)De 1024 a 1048576. O padrão é 262144 (256 KiB).

Como escrever um caminho:

  • body: seguido do caminho com pontos: body:data.customer.id. Para um item de lista, use o número da posição, começando em zero: body:data.items.0.id.
  • header: seguido do nome do cabeçalho: header:X-GitHub-Event.
  • Só entram valores simples (texto, número ou verdadeiro/falso), com até 256 caracteres. Um objeto, uma lista ou um campo vazio contam como "sem valor".
  • Campos enviados e Campos descartados usam o caminho sem o prefixo body:. Num campo descartado, um caminho sem posição vale para cada item da lista: data.cartoes.numero apaga numero de todos os cartões.
  • Um caminho que Campos descartados apague não pode ser usado como tipo, id da entrega ou chave de correlação: a plataforma recusa essa configuração ao salvar.
Dado pessoal

Se o corpo costuma trazer CPF, telefone ou cartão, descarte esses campos. Ao preencher Campos descartados, a tela sugere os mais comuns ("Costuma ter dado pessoal? Descarte…"). Veja também LGPD.

Exemplo​

O sistema de origem manda:

{
"type": "pedido.criado",
"id": "evt_123",
"data": {
"pedido": "1001",
"total": 129.9,
"customer": { "id": "cli_77", "nome": "Maria", "cpf": "000.000.000-00" }
}
}

Com esta configuração:

CampoValor
Onde está o tipo do avisobody:type
Onde está o id da entregabody:id
Onde está a chave de correlaçãobody:data.customer.id
Tipos de aviso aceitospedido.criado, pedido.pago
Campos enviados ao agentetype e data
Campos descartadosdata.customer.cpf

O aviso é aceito (o tipo está na lista), todos os avisos do cliente cli_77 caem na mesma conversa, e o que o agente (e a tela) recebem é:

{
"type": "pedido.criado",
"data": {
"pedido": "1001",
"total": 129.9,
"customer": { "id": "cli_77", "nome": "Maria" }
}
}

O tipo, o id da entrega e a chave de correlação são lidos do corpo original, antes do filtro; o schema também confere o corpo original.

Depois do filtro, o conteúdo guardado e entregue ao agente tem no máximo 32 KiB. Passando disso, o agente recebe só o começo, marcado como cortado. Para corpos grandes, use Campos enviados ao agente e deixe só o que importa.

Respostas​

A plataforma responde com um código HTTP e, quase sempre, um JSON curto. Os erros trazem um detail com um código curto; a plataforma não diz qual conferência de autenticação falhou.

CódigoRespostaQuando aconteceO que fazer
200{"status":"accepted","eventId":"…"}O aviso foi recebido e guardado com segurança.Nada: deu certo. Não reenvie.
200{"status":"duplicate","eventId":"…"}Já recebemos um aviso com o mesmo id de entrega nas últimas 24 horas.Nada: o aviso original já está recebido. Não reenvie.
200{"status":"ignored"}O tipo do aviso não está em Tipos de aviso aceitos, e o aviso cumpre o Schema (o schema é conferido primeiro).Nada: foi descartado de propósito. Se não era para ser, inclua o tipo na lista.
400{"detail":"invalid_body"}O corpo não pode ser lido como o tipo configurado: JSON inválido, raiz que não é um objeto, aninhamento excessivo ou formulário ilegível.Não reenvie igual. Corrija o corpo.
401{"detail":"unauthorized"}A autenticação faltou ou está errada: assinatura, token ou usuário e senha.Não reenvie igual. Confira o segredo, o nome do cabeçalho, o prefixo e o formato.
403{"detail":"forbidden"}O endereço de IP de origem não passa pela lista de Quem pode enviar.Não reenvie igual. Ajuste a lista ou envie de um endereço permitido.
404sem corpoA URL não existe: chave errada, URL trocada ou webhook apagado. Por segurança, a plataforma não diz qual dos casos.Não reenvie. Confira a URL; se ela foi trocada, use a nova.
413{"detail":"payload_too_large"}O corpo é maior que o Tamanho máximo do aviso.Não reenvie igual. Envie menos dados ou aumente o limite (até 1 MiB).
415{"detail":"unsupported_media_type"}O Content-Type não é o configurado, ou o corpo veio comprimido (Content-Encoding).Não reenvie igual. Corrija o cabeçalho e envie sem compressão.
422{"detail":"schema_mismatch","errors":[…]}O corpo não cumpre o Schema. errors lista até 5 problemas, cada um com path e message.Não reenvie igual. Corrija o corpo, ou o schema, conforme errors.
429{"detail":"rate_limited"} e Retry-After: 60Muitos avisos de uma vez: o limite é de 600 por minuto para cada webhook e de 600 chamadas por minuto para cada endereço de IP de origem.Reenvie depois de esperar o Retry-After (60 segundos).
500{"detail":"internal_error"}Falha inesperada da plataforma.Reenvie mais tarde.
503{"detail":"queue_unavailable"} ou {"detail":"schema_busy"}, com Retry-After: 5A plataforma não conseguiu guardar o aviso agora, ou a conferência do schema estava ocupada. Nada foi aceito.Reenvie depois de esperar o Retry-After (5 segundos), de preferência com espera crescente.

Em resumo:

  • 2xx — deu certo; não reenvie, mesmo no duplicate e no ignored.
  • 429, 500 e 503 — reenvie depois de esperar. É seguro: com o id da entrega configurado, um aviso que já chegou volta como duplicate, nunca é processado duas vezes.
  • 400, 401, 403, 404, 413, 415 e 422 — reenviar igual não adianta: corrija a causa primeiro.

Um exemplo de 422:

{
"detail": "schema_mismatch",
"errors": [
{ "path": "data.total", "message": "'abc' is not of type 'number'" }
]
}

Na tela, a área Atividade do webhook conta cada desfecho: Recebidos, Registrados, Ignorados, Recusados (400, 415, 422), Falhas de autenticação (401, 403), Acionou o agente e Não entregues (os dois últimos, do modo acionar) e, quando passam de zero, Repetidos, Grandes demais (413) e Acima do limite (429 por webhook). Nem tudo é contado: as respostas 404 e o 429 por endereço de IP não entram em nenhum contador, e um 503 entra só em Recebidos, sem desfecho. É o primeiro lugar para olhar quando algo não chega.

Não processar o mesmo aviso duas vezes​

Sistemas de origem reenviam avisos — quando a resposta demora, quando houve um erro, ou por garantia. Para o mesmo aviso não ser processado duas vezes, a plataforma usa o id da entrega:

  • Configure Onde está o id da entrega com o campo que identifica o aviso, por exemplo header:X-GitHub-Delivery ou body:id.
  • Um segundo aviso com o mesmo id, no mesmo webhook, em até 24 horas, recebe 200 com duplicate e não é processado de novo. A resposta traz o mesmo eventId do primeiro.
  • O id vale por webhook: o mesmo id em dois webhooks diferentes não se confunde.
  • Só avisos aceitos gastam o id. Um aviso que foi ignorado, recusado (400, 401, 413, 415, 422…) ou que recebeu 503 não impede o reenvio depois de corrigido.
Sem id da entrega

Em branco, a plataforma usa uma marca do corpo inteiro: dois avisos com o corpo idêntico, byte a byte, em até 24 horas, contam como o mesmo aviso. Se o seu sistema pode mandar dois avisos diferentes com o mesmo corpo, configure o id da entrega ou inclua um campo que mude a cada aviso (um número de sequência, a hora do evento).

Só registrar ou acionar o agente​

Cada webhook tem um modo, que você troca no detalhe dele.

Só registrarAcionar o agente
O que acontece com o avisoAparece na lista Últimos avisos da Atividade, que mostra os mais recentes (não é um histórico completo).Além disso, o agente age sozinho a cada aviso válido, sem esperar por ninguém.
Gasta créditoNão.Sim, a cada aviso.
A resposta do agenteNão existe: não há agente.Fica na conversa, dentro da plataforma. Se você quiser, também é enviada ao seu sistema: veja Receber a resposta do agente.
O agente precisa ser públicoNão.Sim.
Quando usarPara testar a integração e ver o formato real dos avisos.Quando o aviso já chega do jeito certo e o agente tem o que fazer com ele.

Só registrar​

É o modo em que todo webhook nasce. A Atividade mostra o último aviso, os contadores e a lista Últimos avisos. Isso não é um histórico completo: a plataforma guarda um resumo (hora, tipo e desfecho) dos últimos 50 avisos, e a tela lista os mais recentes. Na visão técnica, quando há campos descartados configurados, a tela também mostra o conteúdo dos 5 avisos mais recentes (guardados por 24 horas), já sem os campos descartados. A Instrução fica guardada, mas só vale no modo acionar.

Acionar o agente​

No detalhe do webhook, clique em Passar para acionar o agente, escreva (se quiser) O que o agente deve fazer a cada aviso e confirme em Acionar o agente a cada aviso. Daí em diante:

  • cada aviso válido cria uma mensagem para o agente, numa conversa da plataforma; a Atividade mostra o atalho ver conversa de cada aviso;
  • o agente precisa ser público: a plataforma recusa o modo acionar com um agente privado. Marque o agente como público antes — veja Agente público, que também explica quais ferramentas ele pode usar;
  • cada aviso gasta crédito;
  • avisos com a mesma chave de correlação vão para a mesma conversa; sem chave, cada aviso abre uma conversa nova;
  • a resposta do agente fica na conversa, dentro da plataforma: você a lê pelo atalho ver conversa da Atividade. Para receber a resposta também no sistema de origem, veja Receber a resposta do agente;
  • o agente roda como um canal público (anônimo): só usa as ferramentas que foram liberadas para o público e, por padrão, nenhuma está liberada. Para liberar, veja Ferramentas em conversas anônimas, no guia Agente público;
  • se o agente não puder ser acionado, o aviso aparece em Não entregues na Atividade.

O agente recebe uma mensagem com o nome do webhook, o tipo do aviso e a hora em que chegou; depois a sua Instrução; e então os dados do aviso, já filtrados. Como esses dados vêm de um sistema externo, o agente é orientado a tratá-los como dados, nunca como instruções, e a confirmar na origem antes de agir: o aviso é uma pista, não um fato.

Para voltar atrás, clique em Voltar para só registrar. Se o webhook tem devolução da resposta, a tela pede confirmação e apaga o endereço e o segredo dela.

Veja um aviso real antes de ligar

Deixe o webhook em só registrar até ver chegar um aviso de verdade na Atividade. Assim você confere se o formato, o tipo e os campos descartados estão como esperado antes de o agente começar a agir.

Mudar a autenticação, os endereços permitidos ou o agente de um webhook que já aciona o agente vale na hora, já para o próximo aviso. A tela pede confirmação (Aplicar agora?).

Mandar teste​

Para ver como um aviso fica, sem esperar o sistema de origem, abra o webhook na lista e, na área Atividade, clique em Mandar teste. Escolha:

  • Enviar um aviso de exemplo — a plataforma monta um aviso pequeno e ajusta só três coisas ao seu webhook: o tipo do aviso (o primeiro dos Tipos de aviso aceitos, por exemplo), o id da entrega e a chave de correlação. O exemplo leva também os campos test e message; por isso, com um Schema que exige outros campos ou que não aceita campos a mais, o teste volta 422: nesse caso, use Usar meu próprio exemplo; ou
  • Usar meu próprio exemplo — cole um objeto JSON, de até 32 KiB, no formato que o sistema de origem manda.

Depois, clique em Enviar teste. O teste:

  • passa pelas mesmas regras de leitura de um aviso real — tipos aceitos, campos enviados e descartados, schema e tamanho máximo — e responde com o mesmo desfecho (accepted, ignored…);
  • não precisa de autenticação nem passa pelo filtro de IP: quem manda é você, já logado;
  • é lido sempre como JSON, e não envia cabeçalhos: uma regra que lê um cabeçalho (header:…) não tem o que ler;
  • é um aviso novo a cada clique: nunca volta duplicate;
  • não mexe nos contadores da Atividade, e aparece na lista marcado como teste. Há uma exceção: no modo acionar, se a entrega do teste ao agente falhar de vez, o aviso entra em Não entregues, como um aviso real;
  • no modo acionar, com a devolução da resposta ligada, também envia a resposta do agente ao endereço de devolução, como num aviso real: com a mesma assinatura e o mesmo formato. Esse envio não é contado em nenhum contador e aparece em Respostas enviadas, que não marca o que é teste. Com a devolução pausada ou não configurada, a resposta do teste fica só na conversa;
  • tem um limite de 20 testes por minuto por webhook.
No modo acionar, o teste aciona o agente de verdade

Com o webhook em acionar o agente, o teste aciona o agente de verdade e gasta crédito, como um aviso real. A tela pede a sua confirmação (Acionar o agente de verdade?) antes de enviar e, com a devolução ligada, avisa que a resposta do agente também será enviada ao endereço de devolução.

Com os avisos chegando e, no modo acionar, o agente agindo, o fluxo principal está completo. O que vem a seguir é a parte avançada: levar a resposta do agente de volta ao seu sistema.

Receber a resposta do agente​

Esta é a parte avançada do modo acionar, para quem quer levar a resposta do agente de volta ao próprio sistema. Se ainda não mandou um aviso de ponta a ponta, comece pelo Mandar teste.

No modo acionar o agente, a resposta do agente fica na conversa, dentro da plataforma. Se você quer receber essa resposta no sistema de origem — para mostrá-la ao cliente, gravá-la no pedido ou anexá-la a um chamado —, ligue a devolução da resposta: toda vez que o agente termina de tratar um aviso, a plataforma faz uma chamada POST assinada para um endereço do seu sistema, com a resposta no corpo. É a volta do caminho que o aviso fez na ida.

O endereço de devolução é um endereço do seu sistema, e não a URL do webhook.

Só no modo acionar

A devolução só existe em webhook no modo acionar o agente. No modo só registrar não há agente e, portanto, não há resposta a devolver.

Ligar a devolução​

Como no resto do webhook, só administradores configuram a devolução, e no celular a tela é só de leitura.

  1. Em Configurações → Canais, na seção Webhook, abra o webhook que está em acionar o agente.
  2. Na linha Devolver a resposta, que mostra Não configurado, clique em Configurar devolução.
  3. No diálogo Devolver a resposta do agente, preencha Endereço (URL https) que vai receber a resposta. O próprio diálogo avisa que a resposta do agente a cada aviso será enviada a esse endereço, assinada com um segredo que aparece uma única vez. Confirme em Devolver a resposta para este endereço.
  4. Aparece um cartão com o Segredo da devolução da resposta. Copie o segredo, cadastre-o no sistema que vai receber a resposta e clique em Já copiei.

Pronto: a linha Devolver a resposta passa a mostrar Ligado, com o domínio do endereço e a indicação de que a resposta é assinada com segredo.

Copie o segredo na hora

O segredo da devolução aparece uma única vez. Se perder, use Gerar novo segredo e cadastre o novo no seu sistema.

Com a devolução configurada, o detalhe do webhook ganha quatro botões:

BotãoO que faz
Alterar endereçoTroca o endereço. O segredo atual continua valendo e nenhum segredo novo aparece. A tela não mostra o endereço atual (só o domínio): informe o endereço completo de novo. Se a devolução estava pausada, ela volta a ficar Ligada.
Pausar devoluçãoPara de enviar sem apagar o endereço nem o segredo. A linha passa a mostrar Pausado, e o botão vira Retomar devolução.
Gerar novo segredoO segredo atual deixa de valer e um novo aparece, uma única vez. A tela pede confirmação.
Remover devoluçãoApaga o endereço e o segredo, e a resposta deixa de ser enviada. A tela pede confirmação.

Algumas regras valem para os quatro:

  • A configuração vale na hora, porque a plataforma a confere de novo antes de cada envio. Um endereço ou segredo novo vale também para as respostas que ainda esperam para sair, inclusive as que esperam uma nova tentativa. Se a devolução estiver pausada ou removida nesse momento, a resposta que ia sair é descartada e não é enviada depois, mesmo que você retome. O descarte é silencioso: nada é registrado em Respostas enviadas (veja Acompanhar as respostas enviadas). Se uma tentativa anterior desse aviso tinha falhado, o último item dele continua em Tentando de novo, mesmo sem outra tentativa agendada.
  • A devolução vale para os avisos que o agente tratou enquanto ela está ligada: o que chegou antes de ligar, ou durante uma pausa, não gera resposta no seu sistema. Também não geram resposta os avisos ignorados, recusados ou repetidos (duplicate) e os que ficam em Não entregues porque o agente não chegou a ser acionado (por exemplo, por falta de crédito): como o agente não tratou o aviso, nem a resposta nem um failed chegam ao seu sistema.
  • Voltar para só registrar num webhook com devolução, ligada ou pausada: a tela pede confirmação e apaga o endereço e o segredo. Se você acionar o agente de novo depois, configure a devolução do zero.

O endereço que recebe a resposta​

Para que a devolução não sirva para alcançar redes internas, a plataforma só aceita endereços da internet pública. O endereço precisa:

  • começar com https:// (nunca http://);
  • usar a porta 443: sem porta, ou com :443. Qualquer outra porta é recusada;
  • ser de um site público, com nome de domínio de pelo menos duas partes (hooks.exemplo.com.br). Endereço IP não vale, em nenhuma forma, nem localhost, nomes de rede interna (como .local e .internal) ou nomes de uma palavra só. Um nome de site com acento precisa ser informado na forma xn--…;
  • não ter usuário e senha (https://usuario:senha@…), #fragmento nem espaços, e ter até 2048 caracteres;
  • ter um certificado HTTPS válido e reconhecido: um certificado autoassinado falha com tls_error.

Se a tela recusar o endereço, a frase depende do caso. Com o campo vazio, ela diz Informe o endereço (URL https) que vai receber a resposta. Se o endereço não começa com https://, diz O endereço precisa começar com https://. Para os demais endereços recusados (IP, localhost, rede interna, usuário e senha na URL, porta diferente de 443, entre outros), diz: O endereço precisa ser https, de um site público: sem IP, localhost, rede interna nem usuário e senha na URL.

A plataforma confere o endereço de novo na hora de enviar e olha para onde o nome do site aponta. Se ele apontar, ou passar a apontar, para um endereço interno, o envio falha de vez (ssrf_blocked).

Trate o endereço como uma senha

O endereço pode levar um segredo no caminho ou na query, como muitos serviços de automação fazem (por exemplo, https://hooks.exemplo.com.br/entrada/abc123). A plataforma nunca mostra o endereço de volta: a tela mostra só o domínio. Guarde o endereço completo no seu cofre; se perdê-lo, informe de novo em Alterar endereço.

O que o seu sistema recebe​

A cada aviso que o agente tratou, com sucesso ou não, a plataforma faz um POST para o seu endereço, logo depois de o agente terminar (ou, se ele não terminar, quando a plataforma desiste de esperar: veja status). Os cabeçalhos são:

CabeçalhoValor
Content-Typeapplication/json
X-Webhook-EventSempre turn.completed, qualquer que seja o status: o valor não muda quando o aviso termina em failed, paused ou timeout. O desfecho se lê em status, no corpo.
X-Webhook-DeliveryO id da entrega. É o mesmo em todas as tentativas de enviar a resposta de um aviso: use-o para não processar duas vezes.
X-Webhook-TimestampO instante da tentativa, em segundos desde 1970 (um número inteiro). Muda a cada tentativa.
X-Webhook-Signaturesha256= seguido da assinatura em hexadecimal minúsculo.

O corpo é um JSON em UTF-8, compacto (sem espaços entre os campos):

Corpo recebido
{"eventId":"3f2b8c1a-5d4e-4a7b-9c10-2e6d8f0a1b34","conversationId":"c4a1d7e2-8b36-4f59-a0c3-6d2e9b1f7a58","status":"completed","response":"Seu pedido 1001 foi conferido e já está separado para envio.","errorCode":null,"deliveryId":"eff12aaa-53cc-5235-aedc-4bde2b981bc4","webhookId":"5e8f2a91-3c47-4d06-b1a8-9f0d6c3e7b24","occurredAt":"2026-10-03T14:22:07Z"}
CampoO que é
eventIdO id do aviso: o mesmo eventId que a plataforma devolveu no 200 quando recebeu o aviso. É assim que você liga a resposta ao aviso.
conversationIdA conversa onde o agente tratou o aviso (a mesma do atalho ver conversa da Atividade).
statusComo o aviso terminou: completed, failed, paused ou timeout (veja a tabela abaixo).
responseO texto que o agente escreveu, ou null quando status não é completed. Com completed, pode vir vazio (""), se o agente terminou sem escrever texto: quem repassa a resposta a um usuário final deve tratar o vazio (por exemplo, não enviar uma mensagem em branco). Só texto: arquivos, imagens e resultados de ferramentas não vão nele. Se o agente escreveu mais de uma mensagem no mesmo aviso, elas vêm emendadas, sem separador nem quebra de linha entre uma e outra. Tem no máximo 64 KiB; se for maior, é cortado e termina com …[truncated].
errorCodeUm código curto do motivo, só quando status é failed; nos outros casos, null.
deliveryIdO mesmo valor de X-Webhook-Delivery.
webhookIdO id do webhook (aparece como ID do webhook na visão técnica da tela).
occurredAtData e hora, em formato ISO 8601 e em UTC: de quando o agente terminou (completed e failed) ou de quando a plataforma parou de esperar (paused e timeout). Pode vir com frações de segundo (2026-10-03T14:22:07.123456Z): o exemplo acima mostra a forma curta, e o seu código deve aceitar as duas.

Os quatro valores de status:

statusQuando aconteceresponseerrorCode
completedO agente terminou o tratamento.O texto, que pode ser vazio ("") se o agente não escreveu nada.null
failedO tratamento falhou ou foi cancelado, o aviso saiu da fila da conversa sem o agente rodar (por exemplo, cancelado por uma pessoa), ou a plataforma não conseguiu ler o resultado do turno. Neste último caso o agente pode ter terminado bem: confira a conversa.nullUm código (veja abaixo).
pausedCerca de 30 minutos depois de o aviso ser entregue ao agente, ele ainda esperava uma resposta ou aprovação de uma pessoa na conversa.nullnull
timeoutCerca de 30 minutos depois de o aviso ser entregue ao agente, ele ainda não tinha terminado.nullnull
  • Cada aviso que o agente tratou gera uma única devolução. Depois de um paused ou de um timeout, se o agente terminar mais tarde, a plataforma não envia outra devolução: essa resposta fica só na conversa.
  • Aviso que o agente não chegou a tratar não gera devolução, nem failed. Se o aviso ficou em Não entregues porque o agente não pôde ser acionado (por exemplo, por falta de crédito), o seu sistema não recebe nada: o failed é só para um aviso que chegou ao agente. Esse aviso aparece na Atividade, não em Respostas enviadas.
  • Um failed pode demorar cerca de 1 minuto a mais que um completed, porque a plataforma espera um pouco antes de dar a falha como definitiva: o agente pode tentar de novo sozinho.
  • O errorCode de um failed pode ser, por exemplo, generation_stopped (alguém parou o agente na conversa), cancelled (o aviso foi cancelado sem outro código, como um que ainda esperava na fila), unknown_error (falha sem código), timeline_unavailable (a plataforma não conseguiu ler o resultado do turno) ou um código curto do próprio motivo da falha (letras, números, _, . e -, até 64 caracteres). Trate-o como um texto qualquer: a lista não é fechada, não dependa dela.
  • O corpo inteiro pode ser maior que o response, porque caracteres especiais ocupam mais espaço em JSON. Se o seu servidor limita o tamanho do corpo, deixe o limite folgado (1 MiB, por exemplo).

Conferir a assinatura​

Qualquer pessoa que descubra o seu endereço pode mandar uma chamada falsa. A assinatura prova que a chamada veio da plataforma e que ninguém mexeu no corpo. É a mesma conta da Assinatura (HMAC) dos avisos, com duas diferenças: o segredo é o segredo da devolução, e o que se assina é o timestamp, um ponto e o corpo.

  1. Leia o corpo cru, exatamente como chegou, byte a byte. Não leia como JSON para serializar de novo: espaços e ordem dos campos mudam a assinatura.
  2. Monte a mensagem: o valor de X-Webhook-Timestamp, um ponto (.) e o corpo.
  3. Calcule o HMAC-SHA256 da mensagem, usando o segredo da devolução como texto, do jeito que a tela mostrou (sem decodificar), em hexadecimal minúsculo. Escreva sha256= na frente.
  4. Compare com X-Webhook-Signature em tempo constante (hmac.compare_digest no Python, crypto.timingSafeEqual no Node.js), e não com ==.
  5. Recuse a chamada se o timestamp estiver longe do relógio do seu servidor. Nós recomendamos 5 minutos: é uma recomendação, a plataforma não impõe esse valor. Isso impede que alguém reenvie uma chamada que gravou. Mantenha o relógio do seu servidor certo: recusar com 401 é uma falha definitiva, e a plataforma não tenta de novo.
  6. Não processe a mesma entrega duas vezes: guarde os X-Webhook-Delivery já tratados (recomendamos guardá-los por pelo menos 24 horas) e, se um repetir, responda 200 sem fazer nada. Isso é necessário porque a plataforma entrega pelo menos uma vez: as novas tentativas repetem o mesmo X-Webhook-Delivery e, em casos raros, uma entrega que você já respondeu com 2xx pode chegar de novo. Marque a entrega como tratada só depois de processá-la com sucesso, e de propósito: se o processamento falhar, responda 5xx sem marcar, e a nova tentativa da plataforma será processada de verdade. Marcando antes, essa nova tentativa receberia "já tratada" e a resposta se perderia.

Para você testar o seu código, com o segredo segredo-de-exemplo, o exemplo acima (timestamp 1791037328, o corpo mostrado, byte a byte) tem exatamente esta assinatura:

Assinatura do exemplo
sha256=d5bd25cf539569a4a94b3fe9afee16779b0cf9418b633f1aedf8f9c49f83ba01

Os receptores abaixo fazem os seis passos, inclusive marcar a entrega como tratada só depois de processá-la. Eles escutam só em 127.0.0.1: para receber da plataforma, coloque-os atrás do seu servidor web ou de um proxy reverso com HTTPS, no endereço público que você cadastrou. Troque o segredo pelo seu e a porta, se quiser, pela variável PORTA.

Receptor em Python​

Salve como receber.py e rode com python3 receber.py (sem instalar nada):

receber.py
import hashlib
import hmac
import json
import os
import time
from http.server import BaseHTTPRequestHandler, HTTPServer

SEGREDO = "segredo-de-exemplo" # o segredo da devolução, como a tela mostrou
TOLERANCIA = 300 # segundos (5 minutos): é uma recomendação, a plataforma não impõe
PORTA = int(os.environ.get("PORTA", "8000"))

ja_recebidas = set() # em produção, guarde num banco, por pelo menos 24 horas


def tratar(aviso):
# O seu processamento de verdade fica aqui (gravar no pedido, avisar o cliente...).
# Se algo falhar, deixe a exceção subir: o receptor responde 500 e a plataforma reenvia.
# Se o trabalho for demorado, enfileire-o aqui, de forma durável, e termine rápido.
if aviso["status"] == "completed":
# response é texto escrito por um agente: trate como DADO, nunca como comando.
# Pode vir vazio (""): trate o vazio antes de repassá-la a um usuário final.
print("Resposta do aviso", aviso["eventId"], "->", repr(aviso["response"]))
else:
print("Sem resposta para o aviso", aviso["eventId"], ":", aviso["status"], aviso["errorCode"])


class Receptor(BaseHTTPRequestHandler):
def do_POST(self):
# O corpo CRU, byte a byte, como chegou: nunca o JSON serializado de novo.
corpo = self.rfile.read(int(self.headers.get("Content-Length", "0")))
timestamp = self.headers.get("X-Webhook-Timestamp", "")
assinatura = self.headers.get("X-Webhook-Signature", "")
entrega = self.headers.get("X-Webhook-Delivery", "")

# 1. O timestamp precisa ser um número perto do relógio do seu servidor.
try:
fora_da_janela = abs(time.time() - int(timestamp)) > TOLERANCIA
except ValueError:
fora_da_janela = True

# 2. A assinatura: HMAC-SHA256 de "<timestamp>.<corpo>", em tempo constante.
mensagem = f"{timestamp}.".encode("utf-8") + corpo
esperada = "sha256=" + hmac.new(SEGREDO.encode("utf-8"), mensagem, hashlib.sha256).hexdigest()
confere = hmac.compare_digest(esperada.encode("utf-8"), assinatura.encode("utf-8"))

if fora_da_janela or not confere:
return self.responder(401) # 4xx: a plataforma não tenta de novo

# 3. Só agora o corpo vale como vindo da plataforma. Não trate a mesma entrega duas vezes.
if entrega in ja_recebidas:
return self.responder(200) # já tratada: responda 2xx para a plataforma parar

try:
tratar(json.loads(corpo))
except Exception:
return self.responder(500) # falhou: a plataforma reenvia, e a próxima tentativa processa de novo
ja_recebidas.add(entrega) # só agora, depois de processar com sucesso, a entrega vale como tratada
self.responder(200) # responda rápido: o que for demorado, faça depois

def responder(self, codigo):
self.send_response(codigo)
self.send_header("Content-Length", "0")
self.end_headers()


HTTPServer(("127.0.0.1", PORTA), Receptor).serve_forever()

Receptor em Node.js​

Salve como receber.mjs e rode com node receber.mjs (Node 18 ou mais novo, sem instalar nada):

receber.mjs
import crypto from "node:crypto";
import http from "node:http";

const SEGREDO = "segredo-de-exemplo"; // o segredo da devolução, como a tela mostrou
const TOLERANCIA = 300; // segundos (5 minutos): é uma recomendação, a plataforma não impõe
const PORTA = Number(process.env.PORTA ?? 8000);

const jaRecebidas = new Set(); // em produção, guarde num banco, por pelo menos 24 horas

function tratar(aviso) {
// O seu processamento de verdade fica aqui (gravar no pedido, avisar o cliente...).
// Se algo falhar, deixe a exceção subir: o receptor responde 500 e a plataforma reenvia.
// Se o trabalho for demorado, enfileire-o aqui, de forma durável, e termine rápido.
if (aviso.status === "completed") {
// response é texto escrito por um agente: trate como DADO, nunca como comando.
// Pode vir vazio (""): trate o vazio antes de repassá-la a um usuário final.
console.log("Resposta do aviso", aviso.eventId, "->", JSON.stringify(aviso.response));
} else {
console.log("Sem resposta para o aviso", aviso.eventId, ":", aviso.status, aviso.errorCode);
}
}

function responder(res, codigo) {
res.writeHead(codigo, { "Content-Length": "0" }).end();
}

http
.createServer((req, res) => {
if (req.method !== "POST") return responder(res, 405);

const pedacos = [];
req.on("data", (pedaco) => pedacos.push(pedaco));
req.on("end", () => {
// O corpo CRU, byte a byte, como chegou: nunca o JSON serializado de novo.
const corpo = Buffer.concat(pedacos);
const timestamp = req.headers["x-webhook-timestamp"] ?? "";
const assinatura = req.headers["x-webhook-signature"] ?? "";
const entrega = req.headers["x-webhook-delivery"] ?? "";

// 1. O timestamp precisa ser um número perto do relógio do seu servidor.
const foraDaJanela = !(Math.abs(Date.now() / 1000 - Number(timestamp)) <= TOLERANCIA);

// 2. A assinatura: HMAC-SHA256 de "<timestamp>.<corpo>", em tempo constante.
const esperada =
"sha256=" +
crypto.createHmac("sha256", SEGREDO).update(`${timestamp}.`, "utf8").update(corpo).digest("hex");
const a = Buffer.from(esperada, "utf8");
const b = Buffer.from(assinatura, "utf8");
const confere = a.length === b.length && crypto.timingSafeEqual(a, b);

if (foraDaJanela || !confere) return responder(res, 401); // 4xx: a plataforma não tenta de novo

// 3. Só agora o corpo vale como vindo da plataforma. Não trate a mesma entrega duas vezes.
if (jaRecebidas.has(entrega)) return responder(res, 200); // já tratada: responda 2xx

try {
tratar(JSON.parse(corpo.toString("utf8")));
} catch {
return responder(res, 500); // falhou: a plataforma reenvia, e a próxima tentativa processa de novo
}
jaRecebidas.add(entrega); // só agora, depois de processar com sucesso, a entrega vale como tratada
responder(res, 200); // responda rápido: o que for demorado, faça depois
});
})
.listen(PORTA, "127.0.0.1");

Simular a chamada da plataforma​

Com um dos receptores rodando, este comando manda a ele uma chamada assinada como a da plataforma, sem precisar de internet:

Simular a devolução com curl
SEGREDO="segredo-de-exemplo"
TIMESTAMP=$(date +%s)
CORPO='{"eventId":"3f2b8c1a-5d4e-4a7b-9c10-2e6d8f0a1b34","conversationId":"c4a1d7e2-8b36-4f59-a0c3-6d2e9b1f7a58","status":"completed","response":"Seu pedido 1001 foi conferido e já está separado para envio.","errorCode":null,"deliveryId":"eff12aaa-53cc-5235-aedc-4bde2b981bc4","webhookId":"5e8f2a91-3c47-4d06-b1a8-9f0d6c3e7b24","occurredAt":"2026-10-03T14:22:07Z"}'
ASSINATURA=$(printf '%s' "$TIMESTAMP.$CORPO" | openssl dgst -sha256 -hmac "$SEGREDO" | sed 's/^.* //')

curl -i -X POST "http://127.0.0.1:${PORTA:-8000}/" \
-H "Content-Type: application/json" \
-H "X-Webhook-Event: turn.completed" \
-H "X-Webhook-Delivery: eff12aaa-53cc-5235-aedc-4bde2b981bc4" \
-H "X-Webhook-Timestamp: $TIMESTAMP" \
-H "X-Webhook-Signature: sha256=$ASSINATURA" \
-d "$CORPO"

A primeira vez responde 200 e o receptor mostra a resposta. Rodando de novo, o 200 vem sem nada novo no receptor: é a entrega repetida. Troque uma letra do CORPO depois de calcular a assinatura, ou o segredo, e a resposta passa a ser 401.

O que a sua resposta HTTP faz​

O que o seu endereço responde decide o que a plataforma faz com a entrega:

A sua respostaO que a plataforma faz
2xxEntregue. Fim.
408, 429 ou 5xxFalha passageira: tenta de novo, na agenda abaixo.
Sem resposta em 10 segundos, conexão recusada ou interrompida, erro de certificado HTTPS, nome do site que não resolveFalha passageira: tenta de novo.
3xxFalha definitiva: a plataforma não segue redirecionamento. Cadastre o endereço final.
Qualquer outro 4xx (400, 401, 403, 404, 410…)Falha definitiva: não tenta de novo.

Os detalhes:

  • Cada tentativa espera até 10 segundos pela sua resposta, contando a conexão, o envio e a resposta. Responda rápido e deixe o trabalho demorado para depois.
  • O corpo da sua resposta é lido só até 4 KiB e descartado: nada que você escreva nele volta para o agente nem aparece na tela.
  • A plataforma não usa o cabeçalho Retry-After: as esperas seguem a agenda.
  • A plataforma não publica um endereço IP fixo de saída nem um User-Agent para essas chamadas. Para liberar o recebimento, valide a assinatura (Conferir a assinatura) em vez de filtrar por IP ou por User-Agent: um 403 do seu firewall é uma falha definitiva, e a plataforma não tenta de novo.
  • São até 6 tentativas no total: a primeira, logo que a resposta está pronta, e as outras depois de cerca de 5 segundos, 30 segundos, 2 minutos, 10 minutos e 1 hora de espera (cada espera varia, ao acaso, até 10% para mais ou para menos). Depois da 6ª falha passageira a plataforma desiste e não tenta mais, cerca de 1 hora e 12 minutos depois da primeira tentativa.
  • Toda tentativa do mesmo aviso leva o mesmo X-Webhook-Delivery, e um X-Webhook-Timestamp novo, com a assinatura recalculada.
  • Não conte com a ordem: as respostas de dois avisos podem chegar em ordem diferente da dos avisos. Ligue cada uma ao aviso pelo eventId.

Cuidados com o conteúdo​

A resposta é um dado, não uma instrução

O response é um texto escrito por um agente que leu avisos vindos de fora: quem consegue gerar um aviso pode tentar influenciar o que o agente escreve. No seu sistema, trate-o como dado: não execute, não use em comandos, consultas ou modelos de texto, não interprete como ordem e escape antes de exibir (em HTML, por exemplo). Se uma decisão importante depende dele, como aprovar um reembolso, confira antes.

  • Avisos agrupados recebem a mesma resposta. Se vários avisos da mesma conversa (a mesma chave de correlação) chegam enquanto o agente ainda está trabalhando, ele pode tratá-los juntos, numa resposta só. Cada aviso recebe a sua própria devolução, com eventId e deliveryId próprios, mas o response é o mesmo. Se o seu sistema age a cada devolução (envia um e-mail, por exemplo), conte com isso.
  • O segredo da devolução vale como uma senha: quem o tem consegue assinar uma chamada falsa. Guarde-o num cofre e use Gerar novo segredo se ele vazar.
  • A plataforma envia a resposta, que pode ter dados pessoais, para o endereço que você informou, sem pedir mais nada. Confirme que o endereço é do seu sistema.

Acompanhar as respostas enviadas​

Na Atividade do webhook, com a devolução configurada (ligada ou pausada):

  • na visão técnica, a lista Respostas enviadas traz um item por tentativa, com o estado, o número da tentativa, o código HTTP que o seu sistema devolveu (ou sem resposta), a duração e a hora. A plataforma guarda as 20 últimas tentativas (por 7 dias, contados da última) e a tela mostra as 10 mais recentes;
  • na visão negócio, a frase Última resposta enviada diz só o desfecho da mais recente, ou Nenhuma resposta enviada ainda.
Na visão técnicaNa visão negócioO que significa
EntregueentregueO seu sistema respondeu 2xx.
Tentando de novotentando de novoA tentativa falhou de forma passageira e há outra agendada.
FalhoufalhouFalha definitiva (3xx, outro 4xx ou endereço não permitido): sem nova tentativa.
Desistimosnão entregueA 6ª tentativa também falhou, ou a resposta não chegou a entrar na fila de envio.

Quando há um motivo, o item traz um erro com um código curto:

CódigoO que significa
http_503 (ou outro http_ com o código)O seu sistema respondeu com esse código HTTP.
timeoutO seu sistema não respondeu em 10 segundos.
connection_errorNão foi possível conectar, ou a conexão caiu.
tls_errorFalha no certificado HTTPS do seu endereço.
dns_errorO nome do site não foi encontrado, ou a busca demorou mais de 5 segundos.
ssrf_blockedO endereço aponta para uma rede interna ou deixou de ser permitido. Falha definitiva.
redirect_not_followedO seu sistema respondeu 3xx.
callback_queue_fullHavia respostas demais esperando para sair, e esta não foi enviada.
turn_timeout e turn_pausedA entrega deu certo, e o status enviado foi timeout ou paused.
response_truncatedA entrega deu certo, mas o agente produziu tanto conteúdo neste aviso que a plataforma não leu tudo: o response termina com …[truncated]. (Um texto acima de 64 KiB também é cortado e termina assim, sem este código.)

As respostas que não foram entregues não entram no contador Não entregues, que conta avisos que não conseguiram acionar o agente: o lugar para olhar é a lista Respostas enviadas. Uma resposta descartada porque a devolução foi pausada ou removida também não deixa item nessa lista (veja Ligar a devolução).

Teste a sua ponta com o Mandar teste

Com a devolução ligada, o Mandar teste também envia a resposta ao seu endereço, como num aviso real: é a forma mais rápida de ver a chamada chegar. Veja Mandar teste.

Perguntas frequentes​

Posso enviar por GET? Não. A URL do webhook só recebe POST.

Perdi a URL (ou o segredo). Dá para ver de novo? Não: a plataforma mostra a URL e os segredos gerados uma única vez. Use Trocar a URL para gerar um endereço novo; para gerar um novo token ou senha, troque a autenticação para outro tipo e depois de volta.

O mesmo aviso chegou duas vezes. É um erro? Se o sistema de origem reenviou, é normal. Configure Onde está o id da entrega e a segunda cópia volta como duplicate, sem ser processada.

O que o agente vê do aviso? O que sobrar depois de Campos enviados ao agente e Campos descartados, em até 32 KiB, junto com o nome do webhook, o tipo do aviso e a hora em que chegou.

Dá para receber a resposta do agente no meu sistema? Sim, no modo acionar o agente: configure a devolução da resposta e a plataforma envia um POST assinado, com a resposta, ao endereço https que você informar. Veja Receber a resposta do agente.

Quantos avisos por minuto um webhook aceita? Até 600 por minuto. Acima disso, a resposta é 429 com Retry-After: 60.