Todos os outros remetentes de webhooks entregam um número e esperam que você conviva com ele. Os cinco segundos da Shopify, os dez do GitHub, os três do Slack, todos reunidos na nossa referência de provedores de webhook. Você pode reclamar deles, mas não pode mudá-los.
A Twilio é a exceção. Ela expõe os timeouts como parâmetros no próprio webhook, junto com um número de novas tentativas (retries) e uma política que define quais falhas merecem nova tentativa. Cinco ajustes, todos com valores padrão, e na maioria das integrações ninguém nunca mexeu neles.
É uma oportunidade perdida nos dois sentidos, porque os padrões estão errados para os dois casos mais comuns.
TL;DR
cttimeout de conexão: padrão de 5000 ms, faixa de 100 a 10000rttimeout de leitura: padrão de 15000 ms, faixa de 100 a 15000tttempo total, incluindo as novas tentativas: padrão de 15000 ms, faixa de 100 a 15000rcnúmero de novas tentativas: de 0 a 5, padrão 1rppolítica de novas tentativas: quais tipos de falha valem uma nova tentativa- Os tetos são rígidos. Você não consegue comprar mais de 15 segundos de timeout de leitura por preço nenhum
Três timeouts, não um
A maioria das pessoas tem um único modelo mental de timeout de webhook: quanto tempo o remetente espera por uma resposta. A Twilio divide isso em três etapas, e saber em qual delas você está esbarrando muda o diagnóstico por completo.
O parâmetro ct é "o tempo, em milissegundos, que a Twilio espera para estabelecer a conexão TCP com seu servidor". Padrão de 5000 ms, máximo de 10000 ms. Esbarrar nele significa que a Twilio nem conseguiu abrir um socket. Isso é DNS, TLS, um firewall ou um servidor totalmente fora do ar. Nunca é lentidão do seu handler, porque seu handler nem chegou a rodar.
O parâmetro rt é "o tempo, em milissegundos, que a Twilio espera por cada pacote de resposta HTTP depois de enviar a requisição do webhook". Padrão de 15000 ms, máximo de 15000 ms. É nesse que as pessoas pensam quando falam em timeout, e é o prazo real do seu handler.
O parâmetro tt é "o tempo total permitido para todos os timeouts, incluindo as novas tentativas". Padrão de 15000 ms, máximo de 15000 ms. É o teto de toda a sequência de tentativas, e é o parâmetro que pega as pessoas desprevenidas, porque ele também limita as novas tentativas.
Repare que os parâmetros tt e rt têm o mesmo padrão e o mesmo máximo. Com os padrões de fábrica, um único timeout de leitura consome todo o tempo total, e a nova tentativa que você achava ter configurado nunca tem chance de rodar. Se você quer que as novas tentativas sirvam para alguma coisa, precisa baixar o valor de rt para que duas ou mais tentativas caibam dentro de tt.
Essa interação é a coisa mais útil de entender aqui, e ela não é óbvia quando você lê os parâmetros um de cada vez.
Como configurar para que as novas tentativas aconteçam de fato
Suponha que seu handler normalmente responda em 400 ms e que você queira duas novas tentativas antes de desistir.
rt = 3000 // 3s per attempt, generous against a 400ms handler
rc = 2 // two retries
tt = 12000 // room for three attempts plus connect overhead
ct = 3000 // fail fast if the socket will not open
rp = ct,rt,5xx
Agora um handler travado é abandonado em três segundos em vez de quinze, e a nova tentativa chega enquanto o incidente ainda é o mesmo incidente. Compare com os padrões, em que uma única requisição travada queima os quinze segundos inteiros e a nova tentativa configurada nunca dispara, sem nenhum aviso.
Vale pensar com cuidado na política de novas tentativas. Tentar de novo em respostas 4xx costuma ser errado, porque um 400 ou um 404 significa que a própria requisição é inaceitável, e enviá-la de novo vai produzir a mesma resposta. Tente de novo nos timeouts de conexão, nos de leitura e nas respostas 5xx, que são as falhas plausivelmente temporárias. É o mesmo raciocínio de Retry de webhooks: backoff exponencial e jitter, aplicado a um remetente que deixa você expressá-lo na configuração.
A URL de fallback é a outra metade
A documentação da Twilio recomenda timeouts mais curtos "para acelerar o failover para uma URL de fallback em caso de queda no seu servidor".
A URL de fallback é um segundo endpoint que a Twilio chama quando o principal falha, e é o mais perto que um grande remetente chega de um circuit breaker embutido. Ela só ajuda se os timeouts forem curtos o bastante para chegar até ela enquanto quem ligou ainda está na linha, e é exatamente por isso que rt e ct importam para voz e mensagens de um jeito que não importam para um webhook de pagamento.
Aponte-a para algo que não possa falhar pelo mesmo motivo que o principal. Um fallback no mesmo host, atrás do mesmo load balancer, compartilhando o mesmo banco de dados, não é um fallback. TwiML estático servido de um object storage é.
O caso que os ajustes não resolvem
Existe uma categoria que a configuração da Twilio não consegue salvar, e ela é cada vez mais a comum.
Chega um SMS e você quer que um LLM escreva a resposta. Essa chamada leva de vinte a sessenta segundos. O teto do timeout de leitura é quinze, e você não pode aumentá-lo. Não existe combinação de rt, tt e rc que faça um handler de sessenta segundos caber num máximo de quinze.
A resposta é a mesma do Slack: confirme o recebimento e responda separadamente. Devolva um TwiML vazio imediatamente para o relógio da Twilio parar e depois envie a resposta de verdade pela Messages API quando o modelo terminar.
export async function POST(req: Request) {
const raw = await req.text(); // raw body, for the signature
if (!verifyTwilioSignature(req.url, raw, req.headers.get("x-twilio-signature"))) {
return new Response("unauthorized", { status: 403 });
}
await queue.publish(raw); // durable, ~10ms
// Empty TwiML: acknowledged, nothing to say yet
return new Response('<?xml version="1.0" encoding="UTF-8"?><Response></Response>', {
headers: { "content-type": "text/xml" },
});
}
Depois, o worker chama o método messages.create quando tiver uma resposta. O usuário vê a resposta chegar em até um minuto, o que é normal numa conversa por texto. A Twilio viu uma resposta em 40 milissegundos. A versão mais ampla desse argumento está em Webhooks para agentes de IA.
Para voz, o equivalente é devolver um TwiML que toca uma mensagem ou coloca quem ligou numa fila enquanto o trabalho de verdade acontece, porque alguém ouvindo silêncio na linha é uma falha pior do que um SMS lento.
A verificação de assinatura é diferente aqui
O cabeçalho X-Twilio-Signature não é um HMAC simples sobre o corpo. A Twilio monta a string assinada a partir da URL completa da requisição e, nas requisições form-encoded, dos parâmetros do POST ordenados por chave e concatenados. Depois, HMAC-SHA1 com seu auth token, codificado em base64.
Duas consequências práticas.
A URL faz parte da assinatura, então qualquer coisa que a reescreva quebra a verificação. Um proxy que remove uma porta, um load balancer que troca o esquema de https para http ou um framework que reconstrói a URL a partir dos cabeçalhos vão todos produzir uma requisição de aparência válida que falha na verificação. Se a verificação falha em produção e passa localmente, compare a URL exata que seu código está assinando com a que a Twilio chamou.
É SHA1, não SHA256, e a entrada não é o corpo bruto nas requisições form-encoded. Use o helper validateRequest da própria Twilio em vez de adaptar um verificador no estilo da Stripe, porque a construção é realmente diferente. A comparação entre provedores está em Como verificar assinaturas de webhook da Stripe, do GitHub e da Shopify.
Onde o AnyHook entra
Os ajustes são úteis e têm um teto rígido. Quinze segundos é o máximo que a Twilio vai esperar, e muitos handlers legítimos precisam de mais que isso, sobretudo os que chamam um LLM. O verificador de timeout mede seu handler contra esses quinze segundos, e Seu handler de webhook chama um LLM traz a solução de responder primeiro para cada plataforma.
Aponte o webhook para in.anyhook.net/you/twilio e o teto deixa de ser o seu teto. O AnyHook responde em cerca de um segundo a partir da edge (o Cloudflare Worker que atende essa URL) e depois entrega ao seu handler com um prazo de 60 a 300 segundos, dependendo do plano e dentro da franquia de computação dele.
- As novas tentativas contra seu endpoint usam backoff exponencial e duram muito mais do que o
ttpermite - O AnyHook grava cada evento antes da entrega, então um handler com falha vira um replay (um novo envio de um evento armazenado, feito por você), não uma mensagem perdida
- Alertas com 1, 5 e 20 falhas consecutivas, com pausa automática em 20
Se você precisa de TwiML na resposta, mantenha isso no seu próprio handler. Um relay de webhooks (um serviço que recebe, guarda e encaminha seus webhooks) é o formato certo quando o webhook é um aviso, e não um pedido de instruções.
Conclusão
A Twilio dá a você mais controle sobre o tempo dos webhooks do que qualquer outro grande remetente, e os padrões foram pensados para um handler que responde na hora. Baixe o rt para que as novas tentativas caibam no tt, configure o rp para pular os 4xx e aponte a URL de fallback para algo com um modo de falha diferente.
Depois, aceite o teto pelo que ele é. Quinze segundos é o máximo, e, se seu handler precisa de um minuto, nenhuma configuração vai levar você até lá. Confirme o recebimento primeiro e responda por outro canal.