Webhooks6 de agosto de 20268 min de leitura

O Slack dá 3 segundos, e todo mundo vê você perder o prazo

O Slack quer um 2xx em até três segundos e desativa suas inscrições se 95% das entregas falharem em uma hora. Uma falha de webhook que seus usuários veem.

3 SEGUNDOS, DEPOIS TRÊS NOVAS TENTATIVAS QUE SEUS USUÁRIOS VEEMprimeiro envio0 snova tentativa 1~0 snova tentativa 21 minnova tentativa 35 minO QUE O CANAL VÊ SE O HANDLER NÃO FOR IDEMPOTENTEhandler lento4 sposta a mensagemposta de novoe de novo
Três segundos e depois três novas tentativas. Se o handler não for idempotente, cada nova tentativa publica de novo num canal que as pessoas estão lendo.

Seu bot funciona perfeitamente em desenvolvimento. Em produção, ele responde às vezes. Os usuários contam que ele "geralmente" responde, ou que respondeu três vezes a uma mensagem, ou que ficou em silêncio uma tarde inteira e voltou sozinho.

Os três são o mesmo bug. O Slack deu três segundos, seu handler levou quatro, e tudo o que vem depois é a política de novas tentativas (retries) do Slack interagindo com um handler que não foi escrito para esperar por ela.

TL;DR

  • "Seu app deve responder à requisição do evento com um HTTP 2xx em até três segundos"
  • O Slack tenta de novo três vezes: quase imediatamente, depois de 1 minuto e depois de 5 minutos
  • Um handler não idempotente transforma essas novas tentativas em mensagens duplicadas que as pessoas veem
  • x-slack-no-retry: 1 numa resposta diferente de 200 interrompe as novas tentativas de eventos que você sabe que não consegue tratar
  • Se mais de 95% das entregas falharem numa janela de 60 minutos, o Slack desativa suas inscrições de eventos e manda um e-mail para o dono do app

Três segundos não dão para quase nada

Veja o que um handler plausível faz entre receber message.channels e estar pronto para responder. Ele verifica a assinatura. Descobre de qual workspace é o evento. Lê algum estado do seu banco de dados. Depois, se o bot faz qualquer coisa interessante em 2026, ele chama um modelo.

Os três primeiros passos levam uns 200 ms num dia bom. O quarto leva 30 segundos.

É o mesmo descompasso de escala de tempo que aparece com webhooks de pagamento, só que aqui o prazo de resposta são três segundos documentados (o prazo de cada remetente está na nossa referência de provedores de webhook), e a consequência é visível, não silenciosa. Ninguém vê um payment_intent.succeeded falhar. Todo mundo vê um bot não responder.

O formato geral da correção, para qualquer remetente e qualquer plataforma em que você possa hospedar o handler, está em Seu handler de webhook chama um LLM, e o verificador de timeout mede seu handler contra os três segundos do Slack. O Slack também tem uma saída de emergência específica que torna tudo bem menos doloroso do que com a maioria dos remetentes.

O truque que o Slack oferece e a Stripe não

O Slack separa confirmar o recebimento do evento de responder ao usuário. Você responde 200 imediatamente e publica a resposta de verdade depois, usando chat.postMessage ou chamando o response_url que os payloads interativos trazem.

export async function POST(req: Request) {
  const raw = await req.text();
  if (!verifySlackSignature(raw, req.headers)) {
    return new Response("bad signature", { status: 401 });
  }

  const body = JSON.parse(raw);

  // URL verification handshake, only during setup
  if (body.type === "url_verification") {
    return Response.json({ challenge: body.challenge });
  }

  await queue.publish(body);                        // durable, ~10ms
  return new Response(null, { status: 200 });       // well inside 3s
}

O worker então leva o tempo que precisar e chama chat.postMessage quando tiver algo a dizer. Do lado do usuário, isso parece o bot pensando, o que é normal para um bot. Do lado do Slack, o recebimento do evento foi confirmado em 40 milissegundos.

Se o trabalho vai levar mais de um ou dois segundos, publique na hora uma mensagem provisória e atualize-a depois. Uma mensagem que diz "trabalhando nisso" e depois se edita é uma experiência muito melhor do que três segundos de nada seguidos de um timeout.

O que as novas tentativas fazem com você de verdade

O cronograma do Slack é curto e específico: "A primeira nova tentativa será enviada quase imediatamente. A segunda nova tentativa será feita depois de 1 minuto. A terceira e última nova tentativa será enviada depois de 5 minutos."

Agora pense num handler que faz o trabalho primeiro, responde 200 por último e leva quatro segundos. O Slack dá timeout em três. Seu handler termina em quatro e publica uma mensagem. O Slack tenta de novo quase imediatamente, seu handler roda de novo, publica de novo. Um minuto depois, uma terceira mensagem. Cinco minutos depois, uma quarta.

3 SEGUNDOS, DEPOIS TRÊS NOVAS TENTATIVAS QUE SEUS USUÁRIOS VEEMprimeiro envio0 snova tentativa 1~0 snova tentativa 21 minnova tentativa 35 minO QUE O CANAL VÊ SE O HANDLER NÃO FOR IDEMPOTENTEhandler lento4 sposta a mensagemposta de novoe de novo
A sequência de novas tentativas é curta, pública e totalmente visível para as pessoas no canal.

O usuário fez uma pergunta e recebeu quatro respostas, espalhadas ao longo de seis minutos, o que parece menos um bot lento e mais um bot quebrado.

A correção é o mesmo padrão de restrição única de sempre, com o event_id do Slack como chave:

const { rowCount } = await db.query(
  `INSERT INTO processed_events (event_id, provider)
   VALUES ($1, 'slack') ON CONFLICT (event_id) DO NOTHING`,
  [body.event_id],
);
if (rowCount === 0) return new Response(null, { status: 200 });

Repare no 200 no caminho da duplicata. Responder com erro ali diz ao Slack para tentar de novo aquilo que você acabou de ignorar com sucesso. Mais sobre o caso de concorrência, em que duas novas tentativas chegam a duas instâncias no mesmo milissegundo, em Você recebeu o mesmo webhook duas vezes.

O cabeçalho que ninguém usa

Alguns eventos nunca vão dar certo. O workspace desinstalou seu app, o canal foi arquivado, o payload faz referência a um objeto que você excluiu. Tentar de novo esses eventos desperdiça três entregas e, mais importante, empurra sua taxa de falhas para o limite que faz suas inscrições serem desligadas.

O Slack permite recusar as novas tentativas resposta por resposta: "Envie este cabeçalho HTTP e este valor como parte da sua resposta diferente de 200 OK: x-slack-no-retry: 1".

if (!workspace) {
  return new Response("unknown workspace", {
    status: 410,
    headers: { "x-slack-no-retry": "1" },
  });
}

Use-o só para condições definitivas. Um timeout do banco de dados não é definitivo, e você quer essa nova tentativa.

O limite de falhas

Esta é a parte que transforma uma tarde lenta numa queda.

"Se seu app entrar em qualquer combinação dessas condições de falha em mais de 95% das tentativas de entrega dentro de 60 minutos, as inscrições de eventos do seu app serão desativadas temporariamente."

Quando isso acontece, o Slack manda um e-mail para quem criou o app e para o dono dele. O que só é útil se esse endereço pertencer a alguém que ainda está na empresa e ainda o lê. É a mesma armadilha do e-mail de emergência para desenvolvedores da Shopify, descrita em A Shopify excluiu sua inscrição de webhook: o único aviso que você recebe vai para um endereço escolhido durante a configuração e nunca mais revisto.

Vale internalizar: 95% em uma hora não é uma degradação lenta que você vai perceber. Um deploy que quebra a verificação de assinatura chega a 100% na hora, e uma hora depois as inscrições estão desligadas. O bot não dá erro. Ele simplesmente para de receber qualquer coisa, o que, visto de dentro, parece exatamente um canal tranquilo.

Como depurar um bot que responde às vezes

Confira nesta ordem.

O tempo de resposta, não a taxa de erros. Um handler em 2,8 segundos passa em todos os testes que você escreve e falha sempre que o banco de dados fica um pouco ocupado. Meça o p99, não a média, porque é no p99 que o Slack dá timeout.

Cold starts, se você está em serverless. Dois segundos de cold start num prazo de três segundos deixam um segundo para todo o resto. Como receber webhooks na Vercel sem perder nenhum mostra como manter pequeno o grafo de imports da rota, o que devolve mais prazo do que qualquer outra coisa que você possa fazer aqui.

Se as duplicatas são novas tentativas do Slack. Uma mensagem duplicada seis minutos depois da primeira é a terceira nova tentativa do Slack, não um bug na sua lógica de publicação. O intervalo entre elas diz qual é o caso.

Se a inscrição continua ativa. Se tudo ficou em silêncio de uma vez e nada mudou nos seus logs, confira a página Event Subscriptions (inscrições de eventos) do app antes de depurar qualquer outra coisa.

Onde o AnyHook entra

Três segundos é uma restrição de transporte, e transporte é o que um relay de webhooks (um serviço que recebe, guarda e encaminha seus webhooks) substitui.

Aponte a Request URL do Slack para in.anyhook.net/you/slack. O AnyHook verifica e armazena o evento na edge (o Cloudflare Worker do AnyHook) e responde 200 em cerca de um segundo, esteja seu handler quente, frio ou pegando fogo. Quem cumpre o relógio de três segundos do Slack é um worker na edge que só faz duas coisas, então a sequência de novas tentativas quase nunca começa e você nunca chega perto do limite de 95%.

  • Seu handler ganha de 60 a 300 segundos em vez de três, conforme o plano e dentro da franquia de computação dele
  • O AnyHook armazena cada evento antes da entrega, então uma janela de deploy vira um replay (um novo envio de um evento armazenado, feito por você), e não uma série de perguntas dos seus usuários
  • A entrega para seu endpoint é tentada de novo com backoff exponencial por muito mais tempo do que a sequência de seis minutos do Slack
  • Alertas de falha disparam com 1, 5 e 20 falhas consecutivas, bem antes de qualquer coisa chegar a um limite que faça o Slack agir

O AnyHook responde ao handshake de url_verification na edge, depois de verificar a assinatura do Slack se você tiver informado seu segredo de assinatura, e não encaminha nada ao seu handler.

Conclusão

Os três segundos do Slack são o prazo mais apertado que qualquer grande remetente impõe, e um dos poucos em que perder o prazo fica visível para quem usa seu produto. Confirme o recebimento primeiro e responda depois, sempre. Deduplique por event_id antes de fazer qualquer coisa que um humano veria duas vezes. Envie x-slack-no-retry: 1 quando souber que o evento está morto. E vá conferir qual endereço está no app como dono, porque é lá que chega o aviso quando suas inscrições são desligadas.

Todos os posts6 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.