No fim deste guia você tem um endereço do Discord que aceita POST, uma mensagem de teste já dentro do canal e um disparo ligado ao WordPress que avisa a equipe no mesmo minuto em que o artigo entra no ar. E sabe ler o número que volta quando isso para de funcionar sozinho, que é a parte que a maioria dos tutoriais deixa de fora.
Crie o webhook dentro do canal e copie a URL inteira
O webhook nasce no canal que vai receber as mensagens, não no servidor inteiro. Abra as configurações do canal, entre em Integrações, escolha Webhooks e crie um novo. O botão de copiar entrega um endereço com esta forma: https://discord.com/api/webhooks/{id}/{token}.
Os dois trechos finais fazem trabalhos diferentes. O {id} identifica o webhook; o {token} é o que autoriza a escrita. A documentação da Discord é direta sobre o efeito disso: webhooks do not require a bot user or authentication to use. Não existe cabeçalho Authorization nessa chamada. Quem tem a URL, escreve no canal.
Criar um webhook exige a permissão MANAGE_WEBHOOKS. Se a aba Integrações não aparece para você, o cargo não tem essa permissão e nenhum ajuste do seu lado resolve; peça a quem administra o servidor. E copie a URL completa. Parar no {id}, sem o token, é o erro que mais produz 401 no primeiro teste.
Na lista do canal podem aparecer entradas que você não criou. A Discord classifica três tipos de webhook: Incoming, Channel Follower e Application. O que interessa aqui é o Incoming, o único dos três que entrega um token gerado para você usar. Os outros dois existem para seguir canais de anúncio de outros servidores e para interações de aplicativos.
As rotas que funcionam com o token cobrem executar o webhook e editar ou apagar as mensagens que ele mesmo criou. Não há rota para ler o canal, nem para responder a alguém, nem para alcançar outro canal do servidor. Para qualquer coisa além de despejar aviso, o caminho é um bot com autenticação própria, e o custo de manutenção passa a ser outro. A mecânica geral de quem chama quem está no artigo sobre o que é um webhook; aqui o assunto é o endereço específico da Discord.
Dispare um POST de teste antes de ligar em qualquer coisa
Antes de colar essa URL em plugin, script ou automação, prove que ela funciona sozinha:
curl -i -X POST -H "Content-Type: application/json" -d '{"content":"teste"}' "https://discord.com/api/webhooks/ID/TOKEN"
A resposta esperada é 204 No Content, com corpo vazio. Nenhum JSON de confirmação, nenhum identificador de mensagem, só o status. Se você precisar da mensagem criada de volta, acrescente ?wait=true ao endereço e a Discord responde com o objeto da mensagem. Isso passa a valer a pena quando você quer guardar o message.id para editar ou apagar o aviso depois, em PATCH e DELETE sobre /webhooks/{id}/{token}/messages/{message.id}.
O corpo precisa trazer pelo menos um entre content, embeds, components, file e poll. O content aceita até 2000 caracteres. Um JSON vazio volta com erro, e é aí que os números começam a valer alguma coisa:
204sem corpo é o sucesso normal, e o silêncio é esperado.400significa corpo mal formado. Leia o JSON que vem junto:50006é Cannot send an empty message e50035é Invalid form body, que a documentação estende também a invalid Content-Type provided. Esquecer o cabeçalhoContent-Type: application/jsoncai nesse segundo caso.401quer dizer token ausente ou inválido, e no vocabulário da Discord vem acompanhado de50027, Invalid webhook token provided.403é token válido que não tem permissão sobre aquele recurso.404é o webhook que não existe mais, com10015, Unknown webhook.429é limite de requisições, tratado no fim deste guia.
Anote qual desses você recebeu antes de mexer em qualquer outra coisa. Trocar a URL quando o problema era o Content-Type custa uma tarde.
Monte o embed que a equipe vai ler sem abrir o site
Um content de texto puro resolve o aviso mínimo. Para artigo de blog, o embed rende mais: leva título, link, descrição, cor e imagem no mesmo bloco, e quem lê decide se abre o site sem sair do canal. Cada mensagem aceita até dez embeds, bem mais do que qualquer aviso de publicação vai precisar.
Dois parâmetros mudam a cara da mensagem sem exigir webhook novo. username e avatar_url sobrescrevem nome e foto a cada requisição, então o mesmo endereço pode assinar como Blog num disparo e como Deploy noutro. E thread_id, passado na query, entrega a mensagem dentro de uma thread do canal em vez do canal raiz, o que evita transformar o canal principal em mural de log.
Se a ferramenta que você já tem só sabe falar no formato de outro serviço, existem dois atalhos. POST /webhooks/{id}/{token}/slack e POST /webhooks/{id}/{token}/github aceitam cargas no formato do Slack e do GitHub. Reaproveitar um payload que já está pronto costuma sair mais barato que reescrevê-lo.
Quando o embed sai errado, o retorno é 400 com 50035, e o corpo do erro nomeia o campo recusado. Leia esse corpo em vez de tentar variações no escuro: a Discord diz qual campo está fora do formato esperado.
Ligue o disparo ao momento em que o artigo sai
No WordPress, o gancho que corresponde a o artigo entrou no ar é transition_post_status, que recebe três argumentos nesta ordem: $new_status, $old_status e o objeto $post.
O nome engana. Ele dispara em atualização de post, tenha o status mudado ou não. Editar um artigo já publicado entrega publish nos dois primeiros argumentos, e sem uma comparação explícita a equipe recebe um aviso novo a cada correção de vírgula. A guarda cabe em uma linha: se $old_status é igual a $new_status, saia sem fazer nada.
A transição que interessa é qualquer coisa para publish, e a mais esquecida é future para publish, que é o post agendado. Quem testa só publicando na hora descobre semanas depois que nenhum artigo agendado avisou ninguém.
O envio em si é wp_remote_post(). Ele devolve um array com a resposta HTTP quando dá certo e um objeto WP_Error quando falha, então a leitura passa por is_wp_error() antes de qualquer coisa, e o status sai de wp_remote_retrieve_response_code().
O detalhe que morde está no tempo. O timeout padrão da requisição HTTP do WordPress é de 5 segundos. Se a chamada estourar esse limite, você recebe WP_Error, o artigo é publicado do mesmo jeito e ninguém no canal fica sabendo. A publicação não depende do aviso, e é por isso que essa falha passa despercebida.
Há uma tentação no argumento blocking, que vem como true por padrão. Mudar para false devolve o controle ao WordPress sem esperar a resposta e deixa a tela de publicação mais rápida. O preço é perder o código de status, que é justamente o que você vai querer ter na mão quando o aviso sumir. Os outros padrões raramente pedem ajuste: redirection em 5 e sslverify em true servem bem.
Trate a URL como senha e saiba trocá-la sem susto
Como não há autenticação na chamada, a URL é a credencial inteira. Ela num repositório público, numa captura de tela de tutorial ou num campo de plugin sem cuidado significa qualquer pessoa publicando no seu canal, com o nome e a foto que quiser.
Trocar é barato. Apagar o webhook é permanente e responde 204; a partir daí a URL antiga volta 404 com 10015. Regenerar o token mantém o {id} e invalida o que estava salvo, e o sintoma é 401 com 50027. Esses dois pares de números respondem à pergunta mais comum sobre notificação que morreu: 404 é webhook apagado, 401 é token trocado.
Que isso acontece de verdade, e nem sempre por descuido de quem configurou, está registrado nos rastreadores públicos. Uma issue aberta em julho de 2022 no discord.py mostra o erro exato 401 Unauthorized (error code: 50027): Invalid Webhook Token aparecendo no meio de uma sequência de envios que vinha funcionando. Outra, aberta em maio de 2024 no repositório da documentação da própria Discord, relata tokens de interação recusados dentro do prazo de validade de 15 minutos, com 404 antes do prazo e 401 depois. Nenhuma das duas terminou com causa raiz publicada. A leitura prática é que o código precisa reagir a 401 e a 404 em vez de supor que a URL salva vale para sempre.
Existe um teste que não custa nada e não escreve no canal: faça GET na mesma URL. A rota GET /webhooks/{id}/{token} devolve o objeto do webhook sem exigir autenticação, e o objeto vem sem o campo de usuário. Se voltar 200, o endereço está vivo; 401 é token errado; 404 é webhook apagado. Rodar esse GET antes de mexer no WordPress separa problema de configuração de problema de código.
Confirme que o aviso chegou e continua chegando
Publique um rascunho de verdade e cronometre quantos segundos a mensagem leva para aparecer no canal. Esse primeiro teste valida o caminho inteiro, do gancho até a tela de quem lê.
Depois edite o mesmo artigo e confira que nenhuma mensagem nova saiu. Se saiu, falta a comparação entre $old_status e $new_status.
Falta o teste que quase ninguém faz: agende um artigo para dois minutos à frente e espere. O caminho do post agendado não é o mesmo da publicação manual, e é nele que o aviso costuma sumir.
Com os três de pé, sobra o 429. Quando ele aparece, a resposta traz retry_after em segundos, com casas decimais, e um campo global dizendo se o limite é daquela rota ou da conta inteira; os cabeçalhos X-RateLimit-Remaining, X-RateLimit-Reset-After e X-RateLimit-Scope contam o resto da história. Respeitar o retry_after evita um problema maior que o aviso atrasado: a Discord restringe temporariamente, via Cloudflare, o IP que passa de 10.000 requisições inválidas em 10 minutos, contando 401, 403 e 429. Um laço que insiste num webhook morto tira o próprio servidor do ar antes de consertar coisa alguma.
Para quem prefere não manter esse código, o Post Ping monta o embed com imagem, cor e link a cada artigo publicado e dispara no webhook do canal, e roda uma verificação diária dos artigos que ficaram sem notificação, que é a rede de proteção para o timeout de 5 segundos e para o token trocado num domingo. O passo de conexão está na documentação do plugin.
