Resposta rápida
A maioria dos apps reais não é autossuficiente — eles precisam cobrar um cartão pelo Stripe, enviar um SMS pelo Twilio ou saber no instante em que um pagamento é confirmado. Isso significa duas coisas: seu app chamando outros serviços (chamadas de API de saída) e outros serviços chamando de volta o seu app (webhooks de entrada). Erre a direção, a autenticação ou a lógica de retry e você vai lançar algo que funciona no demo e quebra na primeira vez que o Stripe reenviar um webhook que falhou às 2 da manhã.
Por que "é só adicionar uma chamada de API" é onde a maioria das integrações dá errado
Uma chamada de API de saída é a metade fácil. Você acessa o endpoint do Stripe com uma chave secreta, recebe uma resposta, pronto. A metade difícil é o lado de entrada — um webhook é o Stripe (ou Slack, ou Twilio, ou HubSpot) alcançando o seu app sem ser convidado, no ritmo dele, às vezes duas vezes para o mesmo evento.
Já vi times tratarem webhooks como uma requisição POST comum vinda do próprio frontend: sem verificação de assinatura, sem idempotência, sem tratamento de timeout. Funciona bem até um cliente contestar uma cobrança, o Stripe disparar o mesmo evento charge.refunded três vezes porque seu endpoint demorou a responder, e agora o reembolso é processado três vezes no seu banco de dados.
O que realmente precisa acontecer no lado de entrada
Um handler de webhook tem um único trabalho na primeira passada: verificar se é real, confirmar rápido e enfileirar o trabalho de verdade.
Verifique a assinatura. Todo provedor grande assina seus payloads — o Stripe com um header Stripe-Signature, o GitHub com X-Hub-Signature-256. Pule essa checagem e qualquer um que descobrir a URL do seu endpoint pode postar eventos falsos de "pagamento aprovado". Isso não é opcional para nada que envolva dinheiro ou estado de conta.
Responda em menos de alguns segundos. Provedores têm timeouts curtos antes de considerarem a entrega como falha e reenviarem. Se o seu handler faz uma escrita lenta no banco, envia uma notificação no Slack e atualiza um terceiro sistema externo antes de retornar 200, você está pedindo entregas duplicadas. Confirme primeiro, depois processe — seja em um job em segundo plano ou em uma fila leve.
Presuma duplicatas. Guarde o ID do evento e cheque-o antes de processar. Esse único hábito previne quase todo bug de "por que isso disparou duas vezes" em produção.
Onde isso vive em um app feito com a Greta.sh
Quando a Greta.sh estrutura um app full-stack, os handlers de webhook chegam como rotas de API do Next.js com arquivo próprio, separadas da lógica comum do app — então app/api/webhooks/stripe/route.ts não fica misturado com o código que cuida do seu dashboard. Segredos como a signing secret do webhook do Stripe são guardados como variáveis de ambiente, nunca enviados ao bundle do cliente, e o banco de dados com Prisma te dá um lugar direto para registrar IDs de evento nas checagens de idempotência. Você não está montando uma função serverless na mão e torcendo para a config de timeout estar certa — já vem estruturado assim.
Chamadas de saída: a parte que as pessoas subestimam
Chamar uma API de terceiros parece trivial até você ser quem está depurando por que um SMS do Twilio falhou silenciosamente. Algumas coisas importam mais do que parecem:
Rate limits são reais e os provedores os aplicam por chave de API, não por requisição. Se o seu app dispara 50 chamadas de saída em rajada — digamos, notificando todos os usuários de um canal — você vai ser limitado, e seu tratamento de erros precisa tratar um 429 de forma diferente de um 500.
Retries precisam de backoff, não de um loop apertado. Uma chamada que falhou a um processador de pagamentos deve tentar de novo com atraso crescente, não martelar o endpoint cinco vezes em um segundo.
Segredos pertencem ao lado do servidor, sempre. Uma chave de API em um fetch do lado do cliente é uma chave de API que qualquer um pode ler na aba de rede do navegador. Isso parece óbvio escrito assim; ainda é um dos erros mais comuns em apps construídos às pressas.
Uma comparação simples: construir isso na mão vs. estruturado
| Preocupação | Feito na mão do zero | Estruturado pela Greta.sh |
|---|---|---|
| Verificação de assinatura do webhook | Você escreve e testa para cada provedor | Estrutura de rotas pronta; você encaixa a chamada de verificação do provedor |
| Armazenamento de segredos | Fácil vazar para o código do cliente sem querer | Variáveis de ambiente, apenas no servidor por padrão |
| Tratamento de eventos duplicados | Muitas vezes ignorado até quebrar em produção | O schema do Prisma te dá um lugar para registrar IDs de evento desde o dia um |
| Processamento em segundo plano | Fila ou cron customizados | Padrões comuns do Next.js, sem infraestrutura separada para subir |
| Catálogo de conectores | Ler a doc de cada provedor individualmente | Integrações pré-mapeadas na biblioteca de Conectores |
Como isso fica de ponta a ponta
Digamos que você está construindo um app SaaS que recebe pagamentos por assinatura e precisa rebaixar a conta de um usuário no momento em que um pagamento falha. O lado de saída cria a assinatura no Stripe quando o usuário se cadastra. O lado de entrada é um webhook escutando por invoice.payment_failed, verificando a assinatura, checando se ainda não viu aquele ID de evento antes, e então virando uma flag no seu banco de dados que o app já checa a cada carregamento de página.
Nada disso é exótico. São talvez 60 linhas de código depois que você conhece o padrão. O motivo de dar errado não é complexidade — é que os modos de falha (eventos duplicados, handlers lentos, segredos vazados) só aparecem quando tráfego real chega ao endpoint, e aí já é um incidente de produção em vez de um comentário em code review.
Se você está construindo algo como um CRM ou um produto SaaS mais amplo, esse costuma ser o primeiro lugar onde o "funciona na minha máquina" encontra o mundo real — o momento em que seu app precisa conversar com algo fora de si mesmo.
FAQ
Preciso de uma fila de mensagens para webhooks? Não em baixo volume. Uma flag no banco mais uma resposta rápida bastam para a maioria dos apps. Adicione uma fila (como uma tabela de jobs simples que você consulta) quando estiver processando centenas de eventos por minuto ou o trabalho subsequente levar mais do que alguns segundos.
Como testo webhooks localmente? A maioria dos provedores oferece uma CLI para isso — o stripe listen do Stripe encaminha eventos reais para o seu servidor local. Não simule o formato do payload na mão; use a ferramenta do provedor para que a verificação de assinatura realmente bata com a produção.
O que acontece se o meu endpoint estiver fora do ar quando um webhook disparar? Provedores confiáveis reenviam em um cronograma (o Stripe tenta por até três dias). É exatamente por isso que a idempotência importa — o reenvio vai eventualmente ter sucesso, e o seu handler precisa não processar em dobro quando isso acontecer.
Posso conectar APIs que não estão em uma lista de conectores pronta? Sim. Uma biblioteca de conectores acelera os casos comuns, mas qualquer API baseada em REST ou webhook pode ser ligada diretamente — é só uma chamada fetch de saída ou um handler de rota de entrada como qualquer outro.
Encerramento
Webhooks e APIs de terceiros são onde muitos apps "feitos com IA" são julgados, com justiça ou não — é a parte que não dá para fingir com uma UI bonita. Acerte as checagens de assinatura, as confirmações rápidas e a idempotência, e o resto da integração é só encanamento.