Falhas de Webhook em Assinaturas WooCommerce: Como Corrigir

Renovações que falham silenciosamente, cobranças duplicadas, misteriosos 500s. A verdadeira razão pela qual webhooks de assinatura WooCommerce quebram, e como realmente corrigi-los.

31 de março de 2026
Atualizado em 28 de julho de 2026
10 min de leitura
Tags
WooCommerce

Em 2024 fui contratado como líder técnico numa plataforma de e-commerce de produtos regulados no Canadá. Nesse setor, os processadores de pagamento já são melindrosos: nem todo gateway aceita esses produtos, e os que aceitam te fazem sentir a um chargeback de ter a conta encerrada. O modelo de negócio inteiro precisava ser seguro e sustentável.

Então, quando comecei, a primeira coisa que fiz foi "olhar os logs de webhook".

Os logs de webhook diziam que o pedido tinha sido pago. Os pedidos diziam o contrário.

É nesse vão, entre o que o gateway relata e o que o WooCommerce processa de fato, que mora a maioria das falhas de webhook em assinaturas do WooCommerce. Os guias genéricos de solução de problemas não chegam lá. A documentação oficial manda conferir a URL do endpoint, conferir as credenciais e garantir que o site está acessível publicamente. O conselho é tecnicamente certo e praticamente inútil pra qualquer coisa além do caso mais óbvio. Se a URL estivesse errada, você saberia. Se as credenciais fossem inválidas, você veria erros de recusa bem claros.

As falhas que comem a receita de assinatura são mais sutis, e quase sempre são de arquitetura.

O que o WooCommerce Subscriptions faz quando um webhook chega

Quando chega a hora de renovar uma assinatura, o gateway de pagamento não espera o WooCommerce perguntar. Ele dispara um webhook (um POST HTTP pro endpoint do seu site) e espera um 200 em poucos segundos. Do ponto de vista do gateway, é só isso. Manda o payload, recebe o 200, segue a vida.

Do seu lado, o WooCommerce Subscriptions recebe o payload, confere a assinatura digital, acha a assinatura pelos metadados da transação, processa o resultado do pagamento, atualiza o status da assinatura, cria ou atualiza o pedido de renovação, dispara todas as ações penduradas nos hooks e manda uma resposta.

Tudo isso acontece de forma síncrona, durante a requisição do webhook.

Essa é a parte em que quase nenhum dono de loja pensa. O seu site está rodando um fluxo de transação inteiro enquanto o gateway fica ali parado esperando resposta. Se qualquer coisa nesse fluxo demorar demais, estourar uma exceção ou travar o banco, o gateway não vê um 200 limpo. Vê um timeout ou um 500. Registra a entrega como falha e agenda uma nova tentativa.

Dependendo do gateway, você ganha algumas novas tentativas em 24 horas. Uns dão mais. Outros não dão nenhuma.

E a nova tentativa só ajuda se o problema era passageiro. Se é estrutural, um bug criado pelo próprio desenho, toda tentativa falha do mesmo jeito.

Os hooks do WooCommerce Subscriptions que disparam numa renovação

Aquele passo de "disparar todas as ações penduradas nos hooks" não é um hook só. São vários, em sequência, e saber qual você está olhando muda a velocidade com que você acha o bug.

woocommerce_scheduled_subscription_payment começa tudo. É o que o agendador interno do WooCommerce Subscriptions chama quando chega a data da renovação, antes de qualquer dinheiro se mexer. A maioria das integrações de gateway não se pendura direto nele. Elas usam a versão específica do gateway, woocommerce_scheduled_subscription_payment_{$gateway_id}, que é o que dispara a cobrança de fato.

Se a cobrança dá certo, woocommerce_subscription_payment_complete dispara pra qualquer pagamento da assinatura, inicial ou de renovação. woocommerce_subscription_renewal_payment_complete é a versão mais restrita, só pra renovação, e normalmente é a que você quer se estiver montando sua própria camada de log ou de notificação em cima disso.

Se a cobrança falha, dispara woocommerce_subscription_payment_failed, com woocommerce_subscription_renewal_payment_failed como variante de renovação.

Nenhum hook de falha diz o motivo. O motivo continua enterrado no log do gateway e no log do WooCommerce, que eu explico mais abaixo.

Em algum ponto dessa mesma requisição também dispara woocommerce_subscription_status_updated, junto com um hook do status específico, como woocommerce_subscription_status_active ou woocommerce_subscription_status_on-hold. É por esse hook que costuma passar o bug de assinatura em estado inconsistente (o terceiro tipo de falha, lá embaixo).

Acerte o nome dos hooks e você para de chutar onde o plugin estourou a exceção. Erre, e você vai pôr um breakpoint num lugar que nunca dispara.

Os três tipos de falha de webhook de assinatura que você precisa procurar

Falha por timeout. O servidor está sob carga, com um WordPress rodando 40 plugins, vários deles fazendo as próprias requisições HTTP externas no wp_loaded, numa hospedagem compartilhada sem cache de objeto e sem cache de página. O webhook chega, o WooCommerce começa a processar, e a resposta leva 35 segundos. O timeout do gateway é de 30. Ele marca o webhook como expirado e registra falha de entrega.

O WooCommerce pode ter terminado o processamento sem erro nenhum. Os logs do WooCommerce não mostram erro. O status da assinatura foi atualizado certinho. Do ponto de vista do WooCommerce, deu tudo certo. Mas o gateway viu um timeout, então agenda uma nova tentativa. Essa nova tentativa pode processar a mesma renovação de novo, e aí você tem cobrança em dobro, pedido em dobro, ou os dois, dependendo de como o seu site trata ID de transação repetido.

Falha por exceção durante o processamento. Um plugin se pendura em woocommerce_payment_complete e estoura uma exceção não tratada porque não acha um campo meta que esperava encontrar. O WooCommerce Subscriptions já tinha processado o pagamento antes desse hook disparar. O status da assinatura já foi atualizado. Mas a exceção faz o PHP devolver um 500 antes de o seu código mandar resposta pro gateway. O gateway marca como falha e tenta de novo. Agora o mesmo pagamento pode ser processado duas vezes.

Essa é especialmente difícil de achar, porque os logs do WooCommerce mostram a assinatura atualizada com sucesso, e a exceção aconteceu depois. Você precisa olhar os logs fatal-errors ou php-errors exatamente na mesma janela de horário pra encontrar.

Falha por assinatura em estado inconsistente. Alguém do atendimento marcou na mão uma renovação como concluída pra resolver uma reclamação. A assinatura agora está num estado que o WooCommerce Subscriptions não espera quando o gateway dispara a próxima renovação automática. O webhook chega, o WooCommerce confere o status da assinatura, encontra algo inconsistente e pula a renovação em silêncio, ou registra um aviso não crítico que ninguém lê. A assinatura do cliente expira. Ninguém fica sabendo até o cliente procurar o suporte.

Os três aparecem no painel do gateway como "falha na entrega do webhook". Nenhum deles é, de fato. A falha aconteceu dentro do seu WordPress.

Como rastrear uma falha de webhook de assinatura do WooCommerce

O diagnóstico começa com dois logs de eventos separados e uma comparação de horários.

O painel de desenvolvedor do seu gateway de pagamento tem um log de entrega de webhooks. Ache. Ele mostra exatamente quando o webhook disparou, o que foi enviado, que código HTTP voltou, quanto tempo a requisição levou e, na maioria dos gateways, o corpo completo da resposta. Pegue o registro da renovação que falhou.

Depois vá em WooCommerce > Status > Logs e filtre por woocommerce-subscriptions. Ache o registro da mesma assinatura e da mesma data de renovação.

Compare os horários. Se o gateway mostra 200 e o WooCommerce mostra a assinatura atualizada, a falha está mais adiante, provavelmente na expedição, num plugin de notificação ou numa integração externa. Se o gateway mostra timeout, anote a duração da requisição e compare com o tempo médio de resposta do seu servidor naquele horário. Se o gateway mostra 500, olhe os logs woocommerce, wc-fatal-errors e php-errors exatamente naquela janela de horário.

Tem mais um tipo de falha que nem aparece nos logs de webhook: problema no wp-cron.

O WooCommerce Subscriptions usa o wp-cron pra agendar as renovações. Se o wp-cron não é confiável no seu site (e não é em nenhum site sem tráfego constante, porque o wp-cron só dispara quando alguém visita), a renovação pode nunca ser agendada. Não tem webhook pra falhar. A renovação simplesmente não acontece. Uma estratégia de manutenção WordPress séria, com um cron de verdade no servidor chamando o wp-cron.php em horário fixo, elimina essa classe inteira de problema. Deveria ser padrão em qualquer WooCommerce que rode assinaturas.

A solução é sempre de arquitetura

Corrigir falha de webhook uma por uma é olhar pro problema do jeito errado. A pergunta certa é: por que o seu tratamento de webhook faz trabalho bloqueante e síncrono durante uma requisição HTTP com prazo curto?

Na maioria das instalações de WooCommerce e dos plugins sob medida, a resposta é que ninguém pensou em fazer diferente. O padrão dos plugins é processar de forma síncrona porque é a implementação mais simples. Pra loja de pouco volume numa infraestrutura decente, funciona. Pra qualquer coisa com volume real de assinaturas, ou qualquer loja numa infraestrutura que não foi ajustada pra WordPress, é um problema de confiabilidade esperando pra crescer, e desembaraçar isso exige engenharia WooCommerce no nível da infraestrutura.

O padrão que resolve de verdade é processar o webhook de forma assíncrona. Quando o webhook chega, o endpoint faz duas coisas: grava o payload numa fila e devolve 200 na hora. Um processo em segundo plano pega o item da fila e cuida da renovação sem o gateway esperando. Se o processamento falhar, você tem um mecanismo de nova tentativa sob o seu controle, com log, alerta e fila de mensagens mortas, e não o calendário de reenvio caixa-preta do gateway.

A segunda peça é idempotência. O tratamento do webhook precisa conferir se já processou aquele ID de transação do gateway antes de rodar qualquer coisa. É a única proteção estrutural contra cobrança em dobro quando o gateway reenvia um webhook que o seu servidor já processou. O WooCommerce tem algum tratamento de idempotência de fábrica, mas não cobre todos os gateways, e não vai te proteger dos casos extremos.

No servidor, otimizar a velocidade do WordPress ajuda, mas não acaba com o problema. Levar o tempo médio de resposta de 8 segundos pra 800 milissegundos dá mais folga antes do timeout do gateway. Não muda a arquitetura. Você ainda pode ter falha de webhook num pico de carga, numa consulta de banco lenta ou quando uma API de terceiros que os seus plugins chamam resolve não responder.

Hospedagem mais rápida diminui a frequência da falha. Processamento assíncrono acaba com ela.

A parte que ninguém quer ouvir

A maioria dos post-mortems de falha de webhook de assinatura termina com "mudamos pra uma hospedagem melhor" ou "aumentamos o timeout no gateway". Essas mudanças só diminuem a frequência da falha, sem corrigir o problema.

As correções de verdade (processamento assíncrono, idempotência bem feita, cron no servidor, logs estruturados bons o bastante pra dizer o que aconteceu) exigem tratar a infraestrutura de assinaturas do WooCommerce como infraestrutura de produção, e não como um problema de configuração de plugin que se resolve pelo painel.

É outro tipo de trabalho. E é o trabalho que impede um negócio de assinatura de perder receita em silêncio.

Se você já passou pela lista padrão de solução de problemas e ainda não sabe onde as suas renovações estão falhando, a resposta quase certamente está num dos três padrões acima.


Se a sua loja roda assinaturas em qualquer volume relevante, implemente esses padrões antes do próximo pico de falhas, e não depois. O trabalho de infraestrutura WooCommerce é construído exatamente em volta disso: achar onde a falha mora de verdade e corrigir a estrutura. Me manda uma renovação que está falhando.

Perguntas Frequentes

Leia Mais Artigos

Mais artigos pra ler

Voltar para o Blog