Webhooks3 de agosto de 20268 min de leitura

A Shopify excluiu sua inscrição de webhook. Os 5 segundos explicam tudo.

A Shopify dá cinco segundos ao seu endpoint, tenta de novo oito vezes em quatro horas e remove a inscrição. Nenhum erro. Os pedidos só param de chegar.

OS MESMOS QUATRO PASSOS, EMPILHADOS, CONTRA UM MURO FIXOdia bom1,2 sERP lento7,1 s5 stoda entrega estoura o limite de uma vez, então as oito novas tentativas se esgotam em quatro horasnada mudou no código. a inscrição é excluída até quinta-feira.
Nada neste handler está errado. Ele só precisa terminar antes que a Shopify pare de esperar.

O sintoma é estranhamente silencioso. Os pedidos param de aparecer no seu app. Nenhum erro nos seus logs, porque nada está chegando aos seus logs. Nenhuma entrega com falha no admin da Shopify, porque não sobrou inscrição para mostrar entregas. A Shopify removeu a inscrição, e, a menos que o e-mail de aviso tenha chegado a uma caixa de entrada que alguém lê, o primeiro sinal é um lojista perguntando por que os pedidos dos últimos dois dias sumiram.

As regras de webhook da Shopify são mais rígidas que a maioria, e a rigidez se concentra em um número.

TL;DR

  • Cinco segundos. Esse é todo o seu prazo de resposta, por entrega
  • A Shopify tenta de novo 8 vezes ao longo de 4 horas, e, depois de 8 falhas consecutivas, uma inscrição criada pela Admin API é excluída (as declaradas em shopify.app.toml não são)
  • Do ponto de vista do seu app, a exclusão é silenciosa. Sem tráfego e sem erros, porque não sobrou inscrição
  • Os e-mails de aviso e de exclusão vão para o e-mail de emergência do desenvolvedor da sua conta de Partner, que costuma ser o endereço que ninguém acompanha
  • Recuperar significa recriar a inscrição e preencher a lacuna (backfill) pela Admin API, porque a Shopify não reenvia nada

Para onde vão os cinco segundos

O GitHub dá dez segundos, a Stripe não publica número nenhum, e os outros estão na nossa referência de provedores de webhook. A Shopify espera cinco segundos por uma resposta e trata qualquer coisa mais lenta como entrega com falha.

E cinco não é o piso. O Slack permite três, e é rígido com isso de um jeito que pega de surpresa quem até hoje só integrou lojas. A Twilio é a exceção que mostra como esses números são arbitrários: ela entrega o controle a você e deixa você mesmo definir o timeout, e quase ninguém mexe nele.

Cinco segundos parecem bastante até você anotar o que realmente acontece dentro de um handler típico:

EtapaNormalDia ruim
Cold start (serverless)200 ms2,5 s
Verificação do HMAC2 ms2 ms
Buscar a loja pelo domínio30 ms400 ms
Gravar pedido + itens80 ms1,2 s
Sincronizar com ERP / 3PL600 mstimeout
Enviar e-mail de confirmação250 ms3 s
OS MESMOS QUATRO PASSOS, EMPILHADOS, CONTRA UM MURO FIXOdia bom1,2 sERP lento7,1 s5 stoda entrega estoura o limite de uma vez, então as oito novas tentativas se esgotam em quatro horasnada mudou no código. a inscrição é excluída até quinta-feira.

O caminho feliz leva 1,2 segundo e parece totalmente saudável. Aí seu ERP tem uma manhã lenta, todas as entregas passam de cinco segundos ao mesmo tempo, e você queima as oito novas tentativas (retries) em quatro horas. Nada no seu código mudou. Nada no seu código está errado. Na quinta-feira, a inscrição já não existe.

Dois agravantes são específicos da Shopify.

Promoções relâmpago fazem as falhas acontecerem juntas. Os webhooks chegam na proporção dos pedidos, então o momento em que você mais precisa de orders/create é o momento em que seu banco de dados está mais ocupado e seus tempos de resposta estão piores. A falha acontece justamente quando você menos pode pagar por ela.

Operações em massa geram webhooks em avalanche. Um lojista que edita 5.000 produtos por um app ou por importação de CSV gera 5.000 webhooks products/update em pouco tempo. Agora seu endpoint está limitado pelo pool de conexões do seu próprio banco de dados, e toda entrega acima de cinco segundos conta contra você.

Como a exclusão aparece na prática

Depois de oito tentativas com falha dentro da janela de quatro horas, a Shopify para de tentar de novo aquele evento, e, depois de 8 falhas consecutivas, uma inscrição criada pela Admin API é removida, com um e-mail de aviso para o contato de emergência do desenvolvedor do app. Inscrições específicas do app declaradas em shopify.app.toml não são removidas desse jeito.

A Shopify manda e-mail, sim: um aviso quando detecta falhas repetidas e outro quando a inscrição é excluída. Os dois vão para o e-mail de emergência do desenvolvedor da sua conta de Partner. Na prática, esse endereço foi definido uma vez no onboarding, muitas vezes como o e-mail pessoal de alguém que já saiu da empresa, e esse é o motivo mais comum para uma equipe ser pega de surpresa.

Vá conferir agora qual é esse endereço. Leva trinta segundos e é a coisa mais valiosa deste post.

Depois da exclusão, a consulta webhookSubscriptions não retorna nada para esse tópico, e não há entregas nem falhas, porque não sobrou nada para falhar. Os eventos gerados enquanto a inscrição não existia também não ficam em fila em lugar nenhum. A Shopify não reenvia nada depois que a inscrição é recriada.

É esse último ponto que torna isso caro. Diferente de um endpoint da Stripe desativado, em que os eventos continuam existindo na API por 30 dias, uma inscrição da Shopify excluída significa que esses webhooks simplesmente nunca foram enviados. A recuperação é um trabalho de conciliação pela Admin API, não um replay (um novo envio de um evento armazenado, feito por você).

Plataformas self-hosted não são mais gentis: o WooCommerce também desativa um webhook depois de mais de cinco falhas consecutivas.

Como ficar abaixo de cinco segundos

O único formato confiável é: verificar, persistir, confirmar o recebimento e fazer o trabalho de verdade em outro lugar. O verificador de timeout mostra quanto seu handler passa dos cinco segundos hoje, e, se a etapa lenta for uma chamada ao modelo, Seu handler de webhook chama um LLM mostra o mesmo formato para cada plataforma.

export async function POST(req: Request) {
  const raw = await req.text();               // raw body, required for HMAC
  if (!verifyShopifyHmac(raw, req.headers.get("x-shopify-hmac-sha256"))) {
    return new Response("unauthorized", { status: 401 });
  }

  await queue.publish({                       // durable write, ~10ms
    topic: req.headers.get("x-shopify-topic"),
    shop: req.headers.get("x-shopify-shop-domain"),
    webhookId: req.headers.get("x-shopify-webhook-id"),
    body: raw,
  });

  return new Response(null, { status: 200 }); // well inside 5s
}

Quatro detalhes que pedem precisão.

Leia o corpo bruto, não o JSON já interpretado. O HMAC da Shopify é calculado sobre os bytes exatos, então qualquer middleware que faça o parse e serialize de novo vai mudar a ordem das chaves ou os espaços em branco, e toda verificação de assinatura vai falhar. No App Router do Next.js, chame await req.text() antes de qualquer parse. Veja os detalhes de cada provedor em Como verificar assinaturas de webhook da Stripe, do GitHub e da Shopify.

Verifique antes de enfileirar, não depois. A verificação leva cerca de 2 ms e é a barreira que impede um atacante de encher sua fila. A barreira barata vem primeiro.

Deduplique por X-Shopify-Webhook-Id. Ele não muda entre as novas tentativas do mesmo evento, o que faz dele a chave de idempotência correta. A Shopify vai reenviar, então seu consumidor precisa ser idempotente. Os padrões estão em Você recebeu o mesmo webhook duas vezes.

Cuidado com cold starts. Em serverless, um cold start pode consumir metade do seu prazo antes de seu código rodar. Mantenha pequeno o grafo de dependências da rota de webhook: importar o ORM inteiro e o SDK de admin num handler que só precisa enfileirar bytes é um custo real e evitável.

Como recuperar depois de uma exclusão

Primeiro, recrie a inscrição.

mutation {
  webhookSubscriptionCreate(
    topic: ORDERS_CREATE
    webhookSubscription: {
      callbackUrl: "https://your-app.example.com/webhooks/shopify"
      format: JSON
    }
  ) {
    webhookSubscription { id }
    userErrors { field message }
  }
}

Segundo, concilie a lacuna pela Admin API. Como nada é reenviado, consulte os objetos diretamente para a janela da queda e processe-os pelo mesmo caminho de código que seu consumidor de webhooks usa:

{
  orders(first: 250, query: "updated_at:>2026-07-28T00:00:00Z") {
    edges { node { id name updatedAt displayFinancialStatus } }
  }
}

Passe isso pelo seu consumidor idempotente. O que já foi tratado não faz nada; o que faltou é aplicado. É também o momento em que você descobre se seu consumidor é mesmo idempotente.

Terceiro, descubra quais tópicos você perdeu. Liste as inscrições atuais e compare com o que seu app espera. A exclusão é por inscrição, então é perfeitamente normal perder orders/create enquanto products/update sobreviveu, o que deixa o sintoma ainda mais confuso.

Quarto, corrija o e-mail de emergência do desenvolvedor e acrescente seu próprio monitoramento. Um alerta de "nenhum orders/create recebido em 6 horas durante o horário comercial" teria pegado isso no primeiro dia.

Onde o AnyHook entra

O prazo de cinco segundos é um problema de transporte, e transporte é exatamente o que um relay de webhooks (um serviço que recebe, guarda e encaminha seus webhooks) substitui.

Aponte a inscrição da Shopify para in.anyhook.net/you/shopify. O AnyHook responde 200 em cerca de um segundo, esteja seu ERP saudável ou pegando fogo. Agora o relógio da Shopify corre contra essa resposta, e não contra seu servidor, então a lentidão do seu servidor não dispara mais a contagem regressiva das oito novas tentativas. A única exceção é um evento que o AnyHook não consegue armazenar de jeito nenhum: aí ele responde 503 em vez de 200, e as próprias novas tentativas da Shopify assumem.

  • A lentidão do seu servidor fica desacoplada do relógio da Shopify. Tentamos de novo contra seu servidor após intervalos de 5 minutos, 30 minutos, 3 horas, 18 horas e depois uma vez por dia, de 3 a 10 tentativas conforme o plano. No Free, a última tentativa acontece aos 35 minutos, então uma queda mais longa fica coberta pelo log armazenado e pelo replay
  • Persistimos cada evento, com criptografia em repouso, antes de tentar a entrega, então uma lacuna fica disponível para replay a partir do nosso log, em vez de precisar ser reconstruída pela Admin API
  • Os alertas de sequência de falhas disparam com 1, 5 e 20 falhas consecutivas e vão para um endereço que você controla, com pausa automática em 20
  • O replay com um clique cobre um intervalo de tempo, para a janela em que seu servidor ficou fora do ar, e os replays de eventos com falha não consomem sua cota mensal, embora tenham um limite diário e usem sua franquia de computação

Um app público em várias lojas precisa de um único app do AnyHook (uma URL de entrada para uma origem), não de um por loja: aponte a inscrição de cada loja para a mesma URL de entrada. A Shopify assina os webhooks de app com o client secret do seu app, que é o mesmo para todas as lojas, então é esse o segredo que o AnyHook verifica, e o cabeçalho X-Shopify-Shop-Domain continua dizendo ao seu handler qual loja enviou cada evento.

A regra dos cinco segundos da Shopify diz respeito ao endpoint com que ela fala. Coloque ali um endpoint que responde em cerca de um segundo, e a regra deixa de restringir sua lógica de negócio.

Conclusão

Os cinco segundos da Shopify não são uma meta flexível, e a punição por estourá-los repetidamente é a exclusão, e não um endpoint pausado que você vê num painel. Reduza o caminho da resposta a verificar, persistir e confirmar o recebimento, deduplique por X-Shopify-Webhook-Id e vá conferir para qual endereço aponta o e-mail de emergência do desenvolvedor da sua conta de Partner. É lá que vai cair o único aviso que você vai receber.

Todos os posts3 de agosto de 2026 · 8 min

Pare de perder webhooks.

Troque uma URL. Tenha novas tentativas, um log de eventos e replay com um clique. Ou veja antes: crie uma conta, rode a simulação de queda de 30 segundos e veja um evento sobreviver ao seu servidor fora do ar. Não precisa de servidor.