{"id":74,"date":"2026-08-27T19:25:43","date_gmt":"2026-08-27T22:25:43","guid":{"rendered":"https:\/\/postping.com.br\/blog\/erro-webhook-401-404-429\/"},"modified":"2026-08-27T19:25:43","modified_gmt":"2026-08-27T22:25:43","slug":"erro-webhook-401-404-429","status":"publish","type":"post","link":"https:\/\/postping.com.br\/blog\/erro-webhook-401-404-429\/","title":{"rendered":"Erro de webhook no WordPress e o que dizem 401, 404 e 429"},"content":{"rendered":"<p><code>{\"message\": \"Unknown Webhook\", \"code\": 10015}<\/code> foi o que o servidor guardou na madrugada em que os avisos de artigo novo pararam de chegar no canal da equipe. Ningu\u00e9m viu na hora, porque o disparo do webhook acontece fora da tela: o artigo entra no ar, a p\u00e1gina fica no ar, e a \u00fanica coisa que falta \u00e9 a mensagem que deveria ter aparecido no Discord. Quando algu\u00e9m estranha o sil\u00eancio, j\u00e1 passaram tr\u00eas publica\u00e7\u00f5es.<\/p>\n<p>O caminho curto \u00e9 apagar o webhook, criar outro e colar a URL nova. Funciona parte das vezes, e \u00e9 por isso que ele se repete: quando volta a funcionar ningu\u00e9m descobriu a causa, e o mesmo erro reaparece um m\u00eas depois. O n\u00famero que veio na resposta diz o que aconteceu, e s\u00e3o poucos n\u00fameros.<\/p>\n<h2>404 com o c\u00f3digo 10015: o endere\u00e7o n\u00e3o existe mais<\/h2>\n<p>A documenta\u00e7\u00e3o do Discord define o <code>404<\/code> como o recurso no endere\u00e7o informado n\u00e3o existir. Toda resposta de erro da API traz uma chave <code>code<\/code> pr\u00f3pria e uma <code>message<\/code> mais leg\u00edvel, e o par que interessa aqui \u00e9 <code>10015<\/code>, descrito como <strong>Unknown webhook<\/strong>. A URL de disparo tem o formato <code>POST \/webhooks\/{webhook.id}\/{webhook.token}<\/code>, e o <code>404<\/code> est\u00e1 dizendo que o peda\u00e7o do meio, o id, n\u00e3o corresponde mais a webhook nenhum.<\/p>\n<p>Algu\u00e9m apagou o webhook do outro lado, quase sempre sem m\u00e1 inten\u00e7\u00e3o: cada canal aceita no m\u00e1ximo quinze webhooks, e a API tem c\u00f3digo pr\u00f3prio para o teto, o <code>30007<\/code>, descrito como <strong>Maximum number of webhooks reached (15)<\/strong>. Quando um servidor com muitas integra\u00e7\u00f5es chega perto disso, algu\u00e9m limpa os que parecem sem uso. O do blog \u00e9 sempre candidato, porque dispara uma vez por dia e n\u00e3o tem cara de nada na lista.<\/p>\n<p>Fa\u00e7a um <code>GET<\/code> na mesma URL, sem corpo nenhum: esses endpoints respondem a <code>GET<\/code> devolvendo o objeto do webhook quando ele existe. Se voltar o mesmo <code>404<\/code> com o mesmo <code>10015<\/code>, o problema est\u00e1 no endere\u00e7o e n\u00e3o no que voc\u00ea manda, porque sem corpo n\u00e3o h\u00e1 payload para culpar. Se o objeto vier normalmente, a causa \u00e9 outra.<\/p>\n<h2>401 com o c\u00f3digo 50027: a URL est\u00e1 certa pela metade<\/h2>\n<p>O <code>401<\/code> tem uma descri\u00e7\u00e3o oficial que atrapalha aqui: o cabe\u00e7alho <code>Authorization<\/code> estar ausente ou inv\u00e1lido. S\u00f3 que a URL do webhook carrega a credencial no pr\u00f3prio caminho, depois da \u00faltima barra. N\u00e3o existe cabe\u00e7alho a acrescentar. O que se l\u00ea \u00e9 o c\u00f3digo do corpo, <code>50027<\/code>, descrito como <strong>Invalid webhook token provided<\/strong>.<\/p>\n<p>A diferen\u00e7a entre este caso e o anterior \u00e9 cir\u00fargica: no <code>404<\/code> o id n\u00e3o resolve, no <code>401<\/code> o id resolve e o token est\u00e1 errado. Ou o token foi regenerado do outro lado, o que troca a URL inteira e derruba a antiga na hora, sem conviv\u00eancia entre as duas; ou a URL foi cortada na c\u00f3pia, causa mais chata de achar porque ningu\u00e9m agiu. Campo de configura\u00e7\u00e3o com limite de caracteres, quebra de linha em arquivo, vari\u00e1vel de ambiente truncada numa migra\u00e7\u00e3o de servidor.<\/p>\n<p>Distinguir as duas \u00e9 uma quest\u00e3o de contar. Copie a URL fresca em Configura\u00e7\u00f5es do Servidor, integra\u00e7\u00f5es, e compare com a gravada no site, olhando s\u00f3 o trecho depois da \u00faltima barra. Tamanhos diferentes indicam corte na c\u00f3pia; mesmo tamanho com texto diferente indica token regenerado.<\/p>\n<p>Existe um caso em que esse racioc\u00ednio n\u00e3o vale. Uma <a href=\"https:\/\/github.com\/discord\/discord-api-docs\/issues\/2925\" rel=\"nofollow noopener\" target=\"_blank\">quest\u00e3o aberta no reposit\u00f3rio de documenta\u00e7\u00e3o da API<\/a> relata que certos tokens malformados nesses endpoints devolvem <code>500 Internal Server Error<\/code> em vez do <code>404<\/code> ou do <code>401<\/code> esperados. Um <code>500<\/code> ocasional, portanto, nem sempre \u00e9 o Discord com problema.<\/p>\n<h2>400: o pedido chegou, foi entendido e foi recusado<\/h2>\n<p>Endere\u00e7o certo, token certo, conte\u00fado recusado. O disparo precisa trazer valor em pelo menos um entre <code>content<\/code>, <code>embeds<\/code>, <code>components<\/code>, <code>file<\/code> e <code>poll<\/code>. Um plugin que monta a mensagem a partir do resumo do artigo produz corpo vazio quando o artigo foi publicado sem resumo, e o corpo vazio tem c\u00f3digo pr\u00f3prio, o <code>50006<\/code>, descrito como <strong>Cannot send an empty message<\/strong>. Como ele s\u00f3 aparece naquele artigo, o sintoma parece intermitente sendo determin\u00edstico.<\/p>\n<p>Os limites de tamanho produzem o outro c\u00f3digo frequente. O campo <code>content<\/code> aceita at\u00e9 2000 caracteres e o campo <code>embeds<\/code> aceita um array de at\u00e9 10 objetos. Estourar qualquer um dos dois cai no <code>50035<\/code>, descrito como corpo de formul\u00e1rio inv\u00e1lido, devolvido tanto para <code>application\/json<\/code> quanto para <code>multipart\/form-data<\/code>, ou <code>Content-Type<\/code> inv\u00e1lido. O mesmo <code>50035<\/code> responde quando o site manda os dados como formul\u00e1rio em vez de JSON, depois de algu\u00e9m reescrever a chamada e esquecer do cabe\u00e7alho.<\/p>\n<p>Aqui a resposta ajuda: no <code>50035<\/code>, a mensagem aponta o campo que falhou. Reproduza a chamada pelo terminal e leia o texto inteiro que volta, n\u00e3o s\u00f3 o n\u00famero. Mensagem citando um campo \u00e9 problema de conte\u00fado; mensagem falando do webhook devolve voc\u00ea aos dois casos anteriores.<\/p>\n<h2>429: a resposta j\u00e1 diz quantos segundos esperar<\/h2>\n<p>O <code>429<\/code> significa limite de requisi\u00e7\u00f5es atingido. A resposta traz <code>message<\/code>, <code>retry_after<\/code> e <code>global<\/code> no corpo, mais os cabe\u00e7alhos <code>X-RateLimit-*<\/code>, entre eles <code>X-RateLimit-Remaining<\/code>, <code>X-RateLimit-Reset-After<\/code> e <code>X-RateLimit-Scope<\/code>. O <code>retry_after<\/code> \u00e9 documentado como o n\u00famero de segundos a esperar antes de mandar outra requisi\u00e7\u00e3o.<\/p>\n<p>Um blog que publica um artigo por dia n\u00e3o chega perto desse limite sozinho. Quando o <code>429<\/code> aparece, quase sempre h\u00e1 um segundo ator: outra integra\u00e7\u00e3o no mesmo canal consumindo a mesma cota, ou uma opera\u00e7\u00e3o em massa no pr\u00f3prio site. Reimportar um backup, republicar uma categoria inteira depois de reorganiz\u00e1-la: cada post desses dispara o gancho de publica\u00e7\u00e3o, e duzentos disparos em um minuto encontram o limite que uma publica\u00e7\u00e3o por dia jamais encontraria.<\/p>\n<p>A parte cara n\u00e3o \u00e9 a espera. Respostas <code>401<\/code>, <code>403<\/code> e <code>429<\/code> contam como requisi\u00e7\u00f5es inv\u00e1lidas, e IPs que fazem inv\u00e1lidas demais ficam tempor\u00e1ria e automaticamente restritos de acessar a API, com limiar documentado de <strong>10.000 requisi\u00e7\u00f5es inv\u00e1lidas em 10 minutos<\/strong>. Um plugin que tenta de novo em la\u00e7o contra um webhook morto acumula <code>401<\/code> em velocidade de m\u00e1quina, e a restri\u00e7\u00e3o cai sobre o IP do servidor, dividido com outros sites em hospedagem compartilhada. A documenta\u00e7\u00e3o abre uma exce\u00e7\u00e3o: respostas <code>429<\/code> que v\u00eam com <code>X-RateLimit-Scope: shared<\/code> n\u00e3o s\u00e3o contadas contra voc\u00ea.<\/p>\n<p>Olhe ent\u00e3o <code>retry_after<\/code> e <code>X-RateLimit-Reset-After<\/code>. Valores de poucos segundos indicam rajada, e o que voc\u00ea procura deixa de estar no plugin: est\u00e1 no registro do site, no mesmo hor\u00e1rio, na forma de uma opera\u00e7\u00e3o em lote que ningu\u00e9m associou ao Discord.<\/p>\n<h2>O 204 que parece sucesso e o erro que nunca virou n\u00famero<\/h2>\n<p>As duas falhas mais dif\u00edceis de achar n\u00e3o t\u00eam c\u00f3digo de erro nenhum.<\/p>\n<p>A primeira \u00e9 o <code>204 No Content<\/code>, resposta padr\u00e3o de sucesso do disparo. O par\u00e2metro <code>wait<\/code> vem como <code>false<\/code>, e a documenta\u00e7\u00e3o \u00e9 expl\u00edcita sobre o que isso custa: quando ele \u00e9 <code>false<\/code>, uma mensagem que n\u00e3o \u00e9 salva n\u00e3o retorna erro. O <code>204<\/code> confirma que o Discord aceitou o pedido, n\u00e3o que a mensagem apareceu para algu\u00e9m, e um monitoramento que s\u00f3 verifica se o status ficou abaixo de 400 marca como saud\u00e1vel um webhook que n\u00e3o entrega nada. Passando <code>?wait=true<\/code> na URL, a resposta devolve o corpo da mensagem criada, que \u00e9 a prova que faltava. Se o disparo aponta para um t\u00f3pico, olhe tamb\u00e9m o <code>thread_id<\/code>: a mensagem vai para o t\u00f3pico indicado e o desarquiva automaticamente, ent\u00e3o uma entrega bem-sucedida pode estar num lugar que ningu\u00e9m abre.<\/p>\n<p>A segunda \u00e9 do lado do WordPress, e engana quem l\u00ea log. A camada HTTP mant\u00e9m a conex\u00e3o aberta por um tempo definido em segundos, com padr\u00e3o 5, e devolve um objeto de erro em caso de falha, n\u00e3o um c\u00f3digo de status. Se o Discord demorar seis segundos, ou se o servidor estiver sob carga, o disparo falha sem produzir n\u00famero nenhum para o log guardar. Existe ainda o argumento que define se quem chamou precisa do resultado: desligado, a requisi\u00e7\u00e3o sai e o c\u00f3digo segue sem saber se deu certo.<\/p>\n<p>Nos dois casos voc\u00ea olha a aus\u00eancia, n\u00e3o o conte\u00fado. Repita o disparo com <code>wait=true<\/code> e veja se volta corpo de mensagem; depois procure no registro do site o hor\u00e1rio exato da publica\u00e7\u00e3o. Nenhuma linha ali, em vez de uma linha com c\u00f3digo, \u00e9 tempo limite estourado, n\u00e3o recusa do Discord.<\/p>\n<h2>No Telegram os mesmos problemas t\u00eam outro vocabul\u00e1rio<\/h2>\n<p>Quem avisa a equipe nos dois canais diagnostica duas gram\u00e1ticas. O Telegram n\u00e3o conversa por status HTTP: a resposta malsucedida \u00e9 um JSON com <code>ok<\/code> em falso, um <code>error_code<\/code> inteiro e uma <code>description<\/code> leg\u00edvel, mais um campo opcional que carrega <code>retry_after<\/code>, os segundos a esperar antes de tentar de novo, e <code>migrate_to_chat_id<\/code>, o identificador a usar nas pr\u00f3ximas requisi\u00e7\u00f5es quando o grupo migrou.<\/p>\n<p>O <code>migrate_to_chat_id<\/code> \u00e9 o equivalente do <code>404<\/code> do Discord, com uma diferen\u00e7a: nada foi apagado. O grupo virou supergrupo, o identificador mudou, e o site continua mandando para o antigo. O aviso para de chegar sem que ningu\u00e9m tenha tocado na configura\u00e7\u00e3o, e a resposta entrega o n\u00famero novo. Basta l\u00ea-la.<\/p>\n<p>Nos limites de volume, os n\u00fameros p\u00fablicos do Telegram s\u00e3o mais concretos. Um bot n\u00e3o transmite mais que cerca de 30 mensagens por segundo, a n\u00e3o ser que ative transmiss\u00f5es pagas, e dentro de um grupo o teto \u00e9 de 20 por minuto; passando disso come\u00e7am a chegar erros <code>429<\/code>. Um blog di\u00e1rio n\u00e3o encosta nesses n\u00fameros, um la\u00e7o de repeti\u00e7\u00e3o encosta em menos de um minuto. O token tamb\u00e9m mora na URL, no formato <code>https:\/\/api.telegram.org\/bot&lt;token&gt;\/METHOD_NAME<\/code>, ent\u00e3o o corte na c\u00f3pia produz aqui o mesmo sintoma do Discord.<\/p>\n<p>Nenhum desses casos \u00e9 evit\u00e1vel para sempre. Tokens s\u00e3o regenerados, canais s\u00e3o limpos, servidores ficam lentos na hora errada. O que d\u00e1 para encurtar \u00e9 a dist\u00e2ncia entre a falha e algu\u00e9m saber dela, e \u00e9 ela que decide se o problema custa dois minutos ou tr\u00eas artigos publicados no sil\u00eancio. Por isso o <a href=\"https:\/\/postping.com.br\/recursos\">Post Ping<\/a> n\u00e3o se contenta com o status do disparo: no dia seguinte ele volta e pergunta quais publica\u00e7\u00f5es ficaram sem notifica\u00e7\u00e3o, o que pega inclusive as falhas sem c\u00f3digo. Quem ainda nem criou o webhook encontra o passo a passo em <a href=\"https:\/\/postping.com.br\/blog\/webhook-discord-wordpress\/\">webhook do Discord no WordPress<\/a>, e quem chegou aqui sem saber o que \u00e9 um endere\u00e7o que aceita <code>POST<\/code> come\u00e7a por <a href=\"https:\/\/postping.com.br\/blog\/o-que-e-webhook\/\">o que \u00e9 um webhook<\/a>.<\/p>\n<p>Para separar os casos de uma vez, sem recriar nada: dispare um <code>GET<\/code> na URL, sem corpo, e depois um <code>POST<\/code> com <code>?wait=true<\/code>. O <code>GET<\/code> responde se o endere\u00e7o existe, o <code>wait=true<\/code> responde se o pedido aceito virou mensagem, e o que sobrar entre os dois \u00e9 conte\u00fado recusado, limite de taxa ou tempo limite do seu servidor. Se os dois voltarem limpos e o canal seguir silencioso, o problema nunca foi o webhook: \u00e9 o gancho do WordPress que n\u00e3o dispara.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Erro de webhook no WordPress: leia 401, 404, 429 e o 204 que finge sucesso, confirme a causa real e pare de recriar a URL do Discord a cada sil\u00eancio do canal.<\/p>\n","protected":false},"author":1,"featured_media":73,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-74","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\/74","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=74"}],"version-history":[{"count":0,"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/posts\/74\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/media\/73"}],"wp:attachment":[{"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/media?parent=74"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/categories?post=74"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/postping.com.br\/blog\/wp-json\/wp\/v2\/tags?post=74"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}