Webhooks

Configura Slack, Discord, Microsoft Teams, Google Chat e canais por webhook genéricos.

Como funciona um canal por webhook

Seja qual for a plataforma, todo o canal por webhook fica acessível numa única rota:

https://YOUR_HOST/webhooks/<project>/<provider>/<slug>

Um GET a esse URL responde ao aperto de mão de verificação do fornecedor (o Pepe devolve o desafio que a plataforma envia quando registas o URL pela primeira vez). Um POST, por sua vez, é um evento de entrada: o Pepe resolve a ligação, verifica a assinatura do pedido contra o segredo configurado, extrai a mensagem, corre o agente associado, e entrega a resposta pela própria API do fornecedor. Todo este trabalho decorre em segundo plano, para a plataforma receber a confirmação de imediato, já que fornecedores como a Meta voltam a tentar um webhook lento.

Há uma única rota genérica: adicionar um novo fornecedor nunca implica um novo ponto de acesso.

Precisa de um host público. Os canais por webhook exigem um URL que a plataforma consiga alcançar. Expõe a tua instância do Pepe atrás de um proxy inverso ou de um túnel. Se o PHX_HOST já estiver definido (vê Publicar num servidor), os URLs de retorno já saem certos; define PEPE_PUBLIC_URL para sobrepor isso ou preenchê-lo quando não estiver. Para um túnel rápido enquanto testas, corre pepe serve --tunnel.

Slack, Discord, Microsoft Teams, Google Chat

Estes fornecedores configuram-se pela configuração guiada (ou pelo painel), que pede exatamente os campos que cada um precisa e imprime o URL de retorno a registar:

pepe setup

Escolhe a opção de canal, seleciona o fornecedor e o agente, e introduz as credenciais (qualquer segredo aceita uma referência ${ENV_VAR}). Cada plataforma tem a sua própria página, com os campos e passos específicos: Slack, Discord, Microsoft Teams, Google Chat. Esta página cobre apenas o que é comum a todas elas, e ao WhatsApp.

@Menções em grupos

Slack, Discord (no modo ligado por gateway, vê Discord), Microsoft Teams e Google Chat suportam conversas em grupo e em canal. Aí o bot só responde quando é @mencionado (uma mensagem direta chega sempre ao agente). Um canal que deve responder a tudo, como o que recebe pedidos de outro sistema, configura-se de dentro dele:

/mention off          # responde sem menção, até ao /new
/mention on           # volta a exigir menção, até ao /new
/mention off always   # responde sem menção, mantido após o /new e ao reiniciar
/mention on always    # volta a exigir menção de vez (o predefinido)
/mention              # mostra o que vale aqui

Um /mention off ou /mention on simples vale para esta conversa e é esquecido no /new. Com always, fica guardado para o canal, seja qual for o agente que responde nele. O que foi dito na conversa vence o que foi guardado para o canal, e o /new devolve a decisão ao canal. Uma definição nunca chega a outro canal.

A regra tem dois níveis, como qualquer definição de canal. A ligação guarda o valor predefinido para todos os seus canais: Responder sem ser mencionado no formulário da ligação no painel (mention_optional na configuração, pepe gateway mention SLUG --set optional|required na linha de comandos), desligado até o ligares. Um canal pode ter a sua própria resposta, em qualquer dos sentidos, e essa vence só nesse canal: /mention off always abre um canal de uma ligação que exige menção, /mention on always fecha um canal de uma ligação que responde a tudo. Quando a resposta do canal apenas repetiria a predefinição da ligação, /mention on always limita-se a remover a definição própria do canal. O que vale, do mais forte para o mais fraco: esta conversa (até ao /new), a definição própria do canal, a predefinição da ligação e, por fim, menção obrigatória. /mention sozinho diz qual destes está em vigor. A linha de cada canal na página Channels mostra o mesmo, com a etiqueta próprio ou da ligação e um caminho de volta ao da ligação; pepe gateway mention SLUG --channel C --set optional|required e --default fazem-no a partir da linha de comandos. O modelo não consegue alterar nada disto.

Um canal que responde sem menção continua fora de uma mensagem escrita para outra pessoa. No Slack, Discord, Microsoft Teams e Google Chat, uma mensagem que identifica uma pessoa, um grupo de utilizadores ou o canal inteiro (@here, @channel) e não identifica o bot é para eles, por isso o bot ignora-a; identificar o bot, sozinho ou junto com outros, faz com que ele volte. Uma mensagem de outra aplicação fica de fora da regra, porque o cartão de um ticket pode referir pessoas e continuar a ser trabalho do agente.

Como um comando de canal tem sempre de estar dirigido ao bot para correr em primeiro lugar, o primeiro /mention off precisa de uma @menção genuína (@bot /mention off). Depois disso, o canal já não precisa. O WhatsApp não filtra por menção (responde a tudo), por isso /mention não tem efeito lá.

Alterar isto está reservado aos trainers do canal (a mesma lista de confiança do /agent), porque muda o comportamento do canal para todos os que lá estão. Qualquer pessoa pode na mesma enviar /mention para ver a definição atual.

Escrever um comando no Slack. O próprio cliente do Slack trata tudo o que comece por / como uma tentativa de correr um dos seus próprios comandos de barra, e recusa-se a enviar sequer a mensagem quando não há nenhum registado com esse nome - por isso /mention off escrito diretamente é rejeitado pelo Slack antes de chegar ao Pepe. Escreve um espaço antes da barra ( /mention off) para o enviar como texto normal; o Pepe remove esse espaço antes de comparar com o comando, tal como sempre fez.

Quem pode treinar um canal

O trainers da ligação diz quem pode transformar uma conversa em memória, e também quem pode alterar /agent, /model ... global, /mention e esta mesma definição. Vale para todos os canais da ligação. Quando um canal precisa de outra regra, dá-lhe uma lista própria, a partir de dentro dele:

/trainers                  # mostra quem pode treinar aqui e de onde isso vem
/trainers *                # todos neste canal
/trainers none             # ninguém
/trainers @ana @bruno      # só estas pessoas (uma menção do Slack ou um id)
/trainers default          # volta à lista da ligação

A lista própria do canal é a mais forte: substitui a da ligação só nesse canal, não se soma a ela. Só quem pode treinar o canal agora consegue alterá-la, por isso ninguém se promove a si próprio. É mantida depois do /new e de reinícios. O modelo não a consegue alterar: é uma decisão de uma pessoa, escrita no chat, no painel (a lista de canais de cada ligação, ver abaixo) ou com pepe gateway trainers SLUG --channel C --set ....

No Slack os trainers são as pessoas (os ids de utilizador delas), porque cada mensagem passa a indicar quem a escreveu. Uma lista que citava o id do canal continua a funcionar.

No painel escolhes os treinadores em vez de escrever ids. O Pepe lembra-se de quem escreveu em cada canal (o id e o nome de apresentação, quando a plataforma o dá) e oferece essas pessoas: todos os que falaram na ligação, para a lista da ligação, e quem falou nesse canal, para a lista do canal. Quem ainda não escreveu pode ser acrescentado pelo id. Apagar a ligação remove tudo o que foi guardado sobre os seus canais e pessoas. No Slack, os nomes precisam do âmbito users:read; sem ele, aparecem os ids.

Onde está cada ligação

Na página Channels do painel, o cartão de cada ligação diz de quantos canais, grupos e mensagens diretas já recebeu mensagens (“12 canais”). Abre-o para os ver: o nome, quando a plataforma o indicou (caso contrário, o id), se é um grupo ou uma mensagem direta, e quando chegou a última mensagem. Um canal entra na lista a partir da primeira mensagem que lá chega, tenha o bot respondido ou não, por isso um canal onde o bot apenas escuta também aparece.

Cada linha traz as definições desse canal, que alteras no próprio sítio:

Cada definição leva a etiqueta próprio quando o canal tem valor próprio e da ligação quando herda a predefinição; Usar o da ligação remove apenas o valor próprio do canal. Uma mensagem direta também é um canal, listada e configurável da mesma forma, exceto na menção: uma mensagem direta responde sempre, por isso não tem essa definição.

O formulário da ligação tem também Quem pode enviar mensagens a esta ligação (allowed_numbers): qualquer pessoa, ou só as pessoas escolhidas entre quem já escreveu, mais qualquer id introduzido. Uma mensagem de outra pessoa qualquer é ignorada.

Nomes em vez de ids

Os ids da plataforma (A0C5LM7LHS8, D0C5LMVCHEY, U07ABC) são o que identifica tudo, e ninguém os consegue ler. Por isso uma ligação, um canal e uma pessoa podem receber um rótulo: o lápis junto ao título da ligação (ou o campo Rótulo no formulário dela), junto ao título do canal na sua linha, e junto a uma pessoa nas listas de pessoas. O rótulo aparece em todo o lado onde o painel nomeia essa ligação, canal ou pessoa (cartões, linhas, seletores, confirmações, os destinos das tarefas agendadas e dos watches), com o id pequeno e esbatido ao lado, para distinguir dois com o mesmo nome; sem rótulo, aparece o nome dado pela plataforma, e sem nenhum dos dois, o id. Limpar o rótulo recua pela mesma ordem. Um rótulo é texto para pessoas lerem: nunca serve para comparar ou autorizar seja o que for, que continua a ser por id, e fica à parte do nome da plataforma, para que uma atualização deste nunca escreva por cima do que escreveste. No máximo 60 caracteres. O rótulo de uma pessoa acompanha-a em todos os canais da ligação; o de um canal pertence a esse canal nessa ligação.

O nome chega com a mensagem no Microsoft Teams, no Google Chat, no WhatsApp (o nome do contacto) e no Telegram (o título do grupo). No Slack é consultado uma vez, na primeira mensagem recebida do canal, e precisa do âmbito channels:read (groups:read para um canal privado); sem ele, aparece o id. As mensagens diretas do Slack mostram o id.

O cartão de um bot do Telegram lista da mesma forma os grupos, os tópicos de fórum e as conversas privadas, apenas com o vínculo de agente: no Telegram, a menção e os treinadores definem-se por bot.

Vincular um canal a um agente

/agent NOME vincula esta conversa a um agente de forma permanente - um grupo pode encaminhar o canal “suporte” para o agente de suporte e o canal “engenharia” para o engenheiro, lado a lado, tal como um tópico de fórum do Telegram já consegue (vê Telegram). Mantém-se depois de /new e de reinícios, e é reafirmado a cada mensagem, por isso ganha sempre, seja para onde for que a conversa tenha andado:

/agent engenheiro   # vincula este canal, a partir de agora
/agent              # mostra o vínculo atual
/agent none         # desvincula, volta ao agente predefinido da ligação

É uma decisão permanente, que vale para o canal inteiro, não uma escolha pontual de encaminhamento - por isso está reservada a formadores (a mesma lista de confiança que já controla o /model ... global); qualquer outra pessoa recebe só um “não tens permissão”. Um agente com a ferramenta manage_channel consegue fazer o mesmo por linguagem natural (“vincula este canal ao engenheiro, de forma permanente”) com as ações bind_topic/unbind_topic dela - vê Encaminhamento entre agentes para perceberes a diferença entre isto e o switch_agent, que é propositadamente temporário e é desfeito pelo /new.

Para encaminhar um canal em que não estás a conversar (um agente a configurar o canal de outro agente, por exemplo), o agente usa bind_channel/unbind_channel do manage_channel, indicando o slug da ligação e o id do canal. Ele próprio constrói a chave de encaminhamento, por isso ninguém tem de editar o config.json à mão, e aplica-se sem reiniciar.

Para um canal onde ninguém deve poder trocar o agente que responde - nem temporária nem permanentemente - define agent_switch_locked: true na ligação. Recusa logo o /agent NOME, mesmo para um formador, além do switch_agent e das ações bind_topic/unbind_topic do manage_channel, em qualquer conversa dessa ligação. O /agent sem argumento (estado), /mention, /model e /new continuam a funcionar normalmente. Define com --agent-switch-locked no mix pepe gateway whatsapp add / discord add, ou diretamente no config.json.

Mudar de modelo

Os comandos /model e /models permitem ver ou trocar o modelo de IA que responde, mas só funcionam numa ligação em modo admin com commands ativado (vê a comparação de modos em Canais); em support, são tratados como texto simples. O /models lista os modelos disponíveis para o projeto da ligação; o /model mostra o atual, ou muda-o:

/model openrouter               # pergunta se muda só este chat ou todos
/model openrouter session       # muda só para esta conversa
/model openrouter global        # muda para todos com quem esta ligação fala

Mudar globalmente, para todos com quem a ligação fala, fica reservado aos formadores (a mesma lista de confiança que controla a memória); qualquer outra pessoa numa conversa permitida só consegue mudar a sua própria conversa. Define model_switch_locked: true na ligação para desligar isto por completo para quem não é formador. É o mesmo mecanismo usado pelo WhatsApp; a versão do Telegram acrescenta apenas um seletor com botões em vez de comandos escritos.

Aprovar ferramentas de risco na conversa

Por predefinição, um canal por webhook não tem ninguém a quem perguntar, pelo que uma ferramenta de risco que não esteja no auto_approve do agente é recusada e fica guardada para um operador aprovar pela linha de comandos (mix pepe approvals). Para que as pessoas da conversa decidam ali mesmo, indica os aprovadores na ligação (o campo Quem pode aprovar ações de risco): uma lista explícita, ou ["*"] para todos os da conversa. Assim, quando o agente quer fazer algo arriscado, pergunta na própria conversa, mostra o comando verdadeiro e espera uma resposta escrita:

Reply with: allow / allow all / allow session / deny

Só conta a resposta exata de um aprovador, e ela não é passada ao agente como mensagem. A resposta de qualquer outra pessoa, ou uma frase mais longa que contenha “allow”, é apenas uma mensagem. As duas respostas mais abrangentes (“permitir tudo na sessão” e “sempre”) não podem ser escritas aqui; usa uma superfície com botões para essas. Se ninguém responder em cinco minutos, conta como um não, e o agente é avisado de que ninguém respondeu, e não de que foi recusado.

Uma ligação sem aprovadores próprios usa os trainers, que é como funcionava antes de existir a segunda lista. Deixa os aprovadores em ninguém para desligar a aprovação por texto mesmo onde há trainers. Ensinar o agente e aprovar um comando são poderes diferentes, por isso prefere indicar pessoas específicas em vez de todos.

Sem aprovadores (nem trainers) indicados, nada muda: só corre o que o agente já tem pré-aprovado. Nesse caso o agente explica à pessoa, na própria conversa, porque não conseguiu perguntar e onde resolver: no dashboard, abra Canais, abra a ligação e preencha Quem pode aprovar ações de risco com as pessoas que podem aprovar.

Por baixo do capô: o contrato do fornecedor

Cada canal por webhook não passa de um pequeno módulo que implementa o mesmo contrato, o que garante que todos se comportam de forma consistente, e faz com que uma plataforma nova seja apenas mais um módulo, nunca uma rota nova. Os callbacks são:

Um plugin que implemente este contrato regista-se automaticamente como um novo fornecedor sob o seu próprio name, alcançável pela mesma rota /webhooks/..., sem qualquer ligação extra a fazer.