{"id":11,"date":"2026-08-26T21:17:04","date_gmt":"2026-08-26T21:17:04","guid":{"rendered":"https:\/\/postping.com.br\/blog\/webhook-discord-wordpress\/"},"modified":"2026-08-26T21:17:04","modified_gmt":"2026-08-26T21:17:04","slug":"webhook-discord-wordpress","status":"publish","type":"post","link":"https:\/\/postping.com.br\/blog\/webhook-discord-wordpress\/","title":{"rendered":"Webhook do Discord no WordPress: criar, testar e confiar"},"content":{"rendered":"<p>No fim deste guia voc\u00ea tem um endere\u00e7o do Discord que aceita <code>POST<\/code>, uma mensagem de teste j\u00e1 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\u00famero que volta quando isso para de funcionar sozinho, que \u00e9 a parte que a maioria dos tutoriais deixa de fora.<\/p>\n<h2>Crie o webhook dentro do canal e copie a URL inteira<\/h2>\n<p>O webhook nasce no canal que vai receber as mensagens, n\u00e3o no servidor inteiro. Abra as configura\u00e7\u00f5es do canal, entre em Integra\u00e7\u00f5es, escolha Webhooks e crie um novo. O bot\u00e3o de copiar entrega um endere\u00e7o com esta forma: <code>https:\/\/discord.com\/api\/webhooks\/{id}\/{token}<\/code>.<\/p>\n<p>Os dois trechos finais fazem trabalhos diferentes. O <code>{id}<\/code> identifica o webhook; o <code>{token}<\/code> \u00e9 o que autoriza a escrita. A documenta\u00e7\u00e3o da Discord \u00e9 direta sobre o efeito disso: webhooks <em>do not require a bot user or authentication to use<\/em>. N\u00e3o existe cabe\u00e7alho <code>Authorization<\/code> nessa chamada. Quem tem a URL, escreve no canal.<\/p>\n<p>Criar um webhook exige a permiss\u00e3o <code>MANAGE_WEBHOOKS<\/code>. Se a aba Integra\u00e7\u00f5es n\u00e3o aparece para voc\u00ea, o cargo n\u00e3o tem essa permiss\u00e3o e nenhum ajuste do seu lado resolve; pe\u00e7a a quem administra o servidor. E copie a URL completa. Parar no <code>{id}<\/code>, sem o token, \u00e9 o erro que mais produz 401 no primeiro teste.<\/p>\n<p>Na lista do canal podem aparecer entradas que voc\u00ea n\u00e3o criou. A Discord classifica tr\u00eas tipos de webhook: Incoming, Channel Follower e Application. O que interessa aqui \u00e9 o Incoming, o \u00fanico dos tr\u00eas que entrega um token gerado para voc\u00ea usar. Os outros dois existem para seguir canais de an\u00fancio de outros servidores e para intera\u00e7\u00f5es de aplicativos.<\/p>\n<p>As rotas que funcionam com o token cobrem executar o webhook e editar ou apagar as mensagens que ele mesmo criou. N\u00e3o h\u00e1 rota para ler o canal, nem para responder a algu\u00e9m, nem para alcan\u00e7ar outro canal do servidor. Para qualquer coisa al\u00e9m de despejar aviso, o caminho \u00e9 um bot com autentica\u00e7\u00e3o pr\u00f3pria, e o custo de manuten\u00e7\u00e3o passa a ser outro. A mec\u00e2nica geral de quem chama quem est\u00e1 no artigo sobre <a href=\"https:\/\/postping.com.br\/blog\/o-que-e-webhook\/\">o que \u00e9 um webhook<\/a>; aqui o assunto \u00e9 o endere\u00e7o espec\u00edfico da Discord.<\/p>\n<h2>Dispare um POST de teste antes de ligar em qualquer coisa<\/h2>\n<p>Antes de colar essa URL em plugin, script ou automa\u00e7\u00e3o, prove que ela funciona sozinha:<\/p>\n<p><code>curl -i -X POST -H \"Content-Type: application\/json\" -d '{\"content\":\"teste\"}' \"https:\/\/discord.com\/api\/webhooks\/ID\/TOKEN\"<\/code><\/p>\n<p>A resposta esperada \u00e9 <code>204 No Content<\/code>, com corpo vazio. Nenhum JSON de confirma\u00e7\u00e3o, nenhum identificador de mensagem, s\u00f3 o status. Se voc\u00ea precisar da mensagem criada de volta, acrescente <code>?wait=true<\/code> ao endere\u00e7o e a Discord responde com o objeto da mensagem. Isso passa a valer a pena quando voc\u00ea quer guardar o <code>message.id<\/code> para editar ou apagar o aviso depois, em <code>PATCH<\/code> e <code>DELETE<\/code> sobre <code>\/webhooks\/{id}\/{token}\/messages\/{message.id}<\/code>.<\/p>\n<p>O corpo precisa trazer pelo menos um entre <code>content<\/code>, <code>embeds<\/code>, <code>components<\/code>, <code>file<\/code> e <code>poll<\/code>. O <code>content<\/code> aceita at\u00e9 2000 caracteres. Um JSON vazio volta com erro, e \u00e9 a\u00ed que os n\u00fameros come\u00e7am a valer alguma coisa:<\/p>\n<ul>\n<li><code>204<\/code> sem corpo \u00e9 o sucesso normal, e o sil\u00eancio \u00e9 esperado.<\/li>\n<li><code>400<\/code> significa corpo mal formado. Leia o JSON que vem junto: <code>50006<\/code> \u00e9 <em>Cannot send an empty message<\/em> e <code>50035<\/code> \u00e9 <em>Invalid form body<\/em>, que a documenta\u00e7\u00e3o estende tamb\u00e9m a <em>invalid Content-Type provided<\/em>. Esquecer o cabe\u00e7alho <code>Content-Type: application\/json<\/code> cai nesse segundo caso.<\/li>\n<li><code>401<\/code> quer dizer token ausente ou inv\u00e1lido, e no vocabul\u00e1rio da Discord vem acompanhado de <code>50027<\/code>, <em>Invalid webhook token provided<\/em>.<\/li>\n<li><code>403<\/code> \u00e9 token v\u00e1lido que n\u00e3o tem permiss\u00e3o sobre aquele recurso.<\/li>\n<li><code>404<\/code> \u00e9 o webhook que n\u00e3o existe mais, com <code>10015<\/code>, <em>Unknown webhook<\/em>.<\/li>\n<li><code>429<\/code> \u00e9 limite de requisi\u00e7\u00f5es, tratado no fim deste guia.<\/li>\n<\/ul>\n<p>Anote qual desses voc\u00ea recebeu antes de mexer em qualquer outra coisa. Trocar a URL quando o problema era o <code>Content-Type<\/code> custa uma tarde.<\/p>\n<h2>Monte o embed que a equipe vai ler sem abrir o site<\/h2>\n<p>Um <code>content<\/code> de texto puro resolve o aviso m\u00ednimo. Para artigo de blog, o embed rende mais: leva t\u00edtulo, link, descri\u00e7\u00e3o, cor e imagem no mesmo bloco, e quem l\u00ea decide se abre o site sem sair do canal. Cada mensagem aceita at\u00e9 dez embeds, bem mais do que qualquer aviso de publica\u00e7\u00e3o vai precisar.<\/p>\n<p>Dois par\u00e2metros mudam a cara da mensagem sem exigir webhook novo. <code>username<\/code> e <code>avatar_url<\/code> sobrescrevem nome e foto a cada requisi\u00e7\u00e3o, ent\u00e3o o mesmo endere\u00e7o pode assinar como Blog num disparo e como Deploy noutro. E <code>thread_id<\/code>, 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.<\/p>\n<p>Se a ferramenta que voc\u00ea j\u00e1 tem s\u00f3 sabe falar no formato de outro servi\u00e7o, existem dois atalhos. <code>POST \/webhooks\/{id}\/{token}\/slack<\/code> e <code>POST \/webhooks\/{id}\/{token}\/github<\/code> aceitam cargas no formato do Slack e do GitHub. Reaproveitar um payload que j\u00e1 est\u00e1 pronto costuma sair mais barato que reescrev\u00ea-lo.<\/p>\n<p>Quando o embed sai errado, o retorno \u00e9 <code>400<\/code> com <code>50035<\/code>, e o corpo do erro nomeia o campo recusado. Leia esse corpo em vez de tentar varia\u00e7\u00f5es no escuro: a Discord diz qual campo est\u00e1 fora do formato esperado.<\/p>\n<h2>Ligue o disparo ao momento em que o artigo sai<\/h2>\n<p>No WordPress, o gancho que corresponde a <em>o artigo entrou no ar<\/em> \u00e9 <code>transition_post_status<\/code>, que recebe tr\u00eas argumentos nesta ordem: <code>$new_status<\/code>, <code>$old_status<\/code> e o objeto <code>$post<\/code>.<\/p>\n<p>O nome engana. Ele dispara em atualiza\u00e7\u00e3o de post, tenha o status mudado ou n\u00e3o. Editar um artigo j\u00e1 publicado entrega <code>publish<\/code> nos dois primeiros argumentos, e sem uma compara\u00e7\u00e3o expl\u00edcita a equipe recebe um aviso novo a cada corre\u00e7\u00e3o de v\u00edrgula. A guarda cabe em uma linha: se <code>$old_status<\/code> \u00e9 igual a <code>$new_status<\/code>, saia sem fazer nada.<\/p>\n<p>A transi\u00e7\u00e3o que interessa \u00e9 qualquer coisa para <code>publish<\/code>, e a mais esquecida \u00e9 <code>future<\/code> para <code>publish<\/code>, que \u00e9 o post agendado. Quem testa s\u00f3 publicando na hora descobre semanas depois que nenhum artigo agendado avisou ningu\u00e9m.<\/p>\n<p>O envio em si \u00e9 <code>wp_remote_post()<\/code>. Ele devolve um array com a resposta HTTP quando d\u00e1 certo e um objeto <code>WP_Error<\/code> quando falha, ent\u00e3o a leitura passa por <code>is_wp_error()<\/code> antes de qualquer coisa, e o status sai de <code>wp_remote_retrieve_response_code()<\/code>.<\/p>\n<p>O detalhe que morde est\u00e1 no tempo. O <code>timeout<\/code> padr\u00e3o da requisi\u00e7\u00e3o HTTP do WordPress \u00e9 de 5 segundos. Se a chamada estourar esse limite, voc\u00ea recebe <code>WP_Error<\/code>, o artigo \u00e9 publicado do mesmo jeito e ningu\u00e9m no canal fica sabendo. A publica\u00e7\u00e3o n\u00e3o depende do aviso, e \u00e9 por isso que essa falha passa despercebida.<\/p>\n<p>H\u00e1 uma tenta\u00e7\u00e3o no argumento <code>blocking<\/code>, que vem como <code>true<\/code> por padr\u00e3o. Mudar para <code>false<\/code> devolve o controle ao WordPress sem esperar a resposta e deixa a tela de publica\u00e7\u00e3o mais r\u00e1pida. O pre\u00e7o \u00e9 perder o c\u00f3digo de status, que \u00e9 justamente o que voc\u00ea vai querer ter na m\u00e3o quando o aviso sumir. Os outros padr\u00f5es raramente pedem ajuste: <code>redirection<\/code> em 5 e <code>sslverify<\/code> em <code>true<\/code> servem bem.<\/p>\n<h2>Trate a URL como senha e saiba troc\u00e1-la sem susto<\/h2>\n<p>Como n\u00e3o h\u00e1 autentica\u00e7\u00e3o na chamada, a URL \u00e9 a credencial inteira. Ela num reposit\u00f3rio p\u00fablico, 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.<\/p>\n<p>Trocar \u00e9 barato. Apagar o webhook \u00e9 permanente e responde <code>204<\/code>; a partir da\u00ed a URL antiga volta <code>404<\/code> com <code>10015<\/code>. Regenerar o token mant\u00e9m o <code>{id}<\/code> e invalida o que estava salvo, e o sintoma \u00e9 <code>401<\/code> com <code>50027<\/code>. Esses dois pares de n\u00fameros respondem \u00e0 pergunta mais comum sobre notifica\u00e7\u00e3o que morreu: 404 \u00e9 webhook apagado, 401 \u00e9 token trocado.<\/p>\n<p>Que isso acontece de verdade, e nem sempre por descuido de quem configurou, est\u00e1 registrado nos rastreadores p\u00fablicos. Uma issue aberta em julho de 2022 no discord.py mostra o erro exato <code>401 Unauthorized (error code: 50027): Invalid Webhook Token<\/code> aparecendo no meio de uma sequ\u00eancia de envios que vinha funcionando. Outra, aberta em maio de 2024 no reposit\u00f3rio da documenta\u00e7\u00e3o da pr\u00f3pria Discord, relata tokens de intera\u00e7\u00e3o recusados dentro do prazo de validade de 15 minutos, com <code>404<\/code> antes do prazo e <code>401<\/code> depois. Nenhuma das duas terminou com causa raiz publicada. A leitura pr\u00e1tica \u00e9 que o c\u00f3digo precisa reagir a 401 e a 404 em vez de supor que a URL salva vale para sempre.<\/p>\n<p>Existe um teste que n\u00e3o custa nada e n\u00e3o escreve no canal: fa\u00e7a <code>GET<\/code> na mesma URL. A rota <code>GET \/webhooks\/{id}\/{token}<\/code> devolve o objeto do webhook sem exigir autentica\u00e7\u00e3o, e o objeto vem sem o campo de usu\u00e1rio. Se voltar 200, o endere\u00e7o est\u00e1 vivo; 401 \u00e9 token errado; 404 \u00e9 webhook apagado. Rodar esse <code>GET<\/code> antes de mexer no WordPress separa problema de configura\u00e7\u00e3o de problema de c\u00f3digo.<\/p>\n<h2>Confirme que o aviso chegou e continua chegando<\/h2>\n<p>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\u00e9 a tela de quem l\u00ea.<\/p>\n<p>Depois edite o mesmo artigo e confira que nenhuma mensagem nova saiu. Se saiu, falta a compara\u00e7\u00e3o entre <code>$old_status<\/code> e <code>$new_status<\/code>.<\/p>\n<p>Falta o teste que quase ningu\u00e9m faz: agende um artigo para dois minutos \u00e0 frente e espere. O caminho do post agendado n\u00e3o \u00e9 o mesmo da publica\u00e7\u00e3o manual, e \u00e9 nele que o aviso costuma sumir.<\/p>\n<p>Com os tr\u00eas de p\u00e9, sobra o <code>429<\/code>. Quando ele aparece, a resposta traz <code>retry_after<\/code> em segundos, com casas decimais, e um campo <code>global<\/code> dizendo se o limite \u00e9 daquela rota ou da conta inteira; os cabe\u00e7alhos <code>X-RateLimit-Remaining<\/code>, <code>X-RateLimit-Reset-After<\/code> e <code>X-RateLimit-Scope<\/code> contam o resto da hist\u00f3ria. Respeitar o <code>retry_after<\/code> evita um problema maior que o aviso atrasado: a Discord restringe temporariamente, via Cloudflare, o IP que passa de 10.000 requisi\u00e7\u00f5es inv\u00e1lidas em 10 minutos, contando 401, 403 e 429. Um la\u00e7o que insiste num webhook morto tira o pr\u00f3prio servidor do ar antes de consertar coisa alguma.<\/p>\n<p>Para quem prefere n\u00e3o manter esse c\u00f3digo, 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\u00e7\u00e3o di\u00e1ria dos artigos que ficaram sem notifica\u00e7\u00e3o, que \u00e9 a rede de prote\u00e7\u00e3o para o timeout de 5 segundos e para o token trocado num domingo. O passo de conex\u00e3o est\u00e1 na <a href=\"https:\/\/postping.com.br\/documentacao\">documenta\u00e7\u00e3o do plugin<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Como criar o webhook do Discord, testar a URL com curl antes de confiar nela e ligar no WordPress sem aviso perdido: 204, 400, 401, 404 e 429 explicados.<\/p>\n","protected":false},"author":1,"featured_media":10,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-11","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-webhooks"],"_links":{"self":[{"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/posts\/11","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/comments?post=11"}],"version-history":[{"count":0,"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/posts\/11\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/media\/10"}],"wp:attachment":[{"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/media?parent=11"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/categories?post=11"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/tags?post=11"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}