POST assinado para lá.
Os dois são coisas separadas de propósito. Se a entrega falhar, o evento continua existindo: você consulta em GET /api/v1/events e reenvia quando o seu servidor voltar.
Configurar
1
Crie uma rota no seu app
Uma rota HTTP pública que aceita
POST com JSON, em qualquer linguagem.2
Cadastre o endpoint no painel
Em Configurações → Webhooks, clique em Novo endpoint e cole a URL. Em produção ela precisa ser
https://: o corpo leva o nome e o e-mail do seu cliente, a assinatura prova quem mandou, e o TLS é o que impede que alguém leia isso no caminho. No sandbox http:// também vale, que é o que faz um túnel para a sua máquina funcionar.O endereço também precisa ser da internet pública. Uma URL que resolve para a rede interna — 127.0.0.1, 10.x.x.x, 192.168.x.x, 169.254.169.254 — é recusada no cadastro, e recusada de novo na hora de cada entrega, caso o DNS passe a responder isso depois. A recusa aparece na lista de entregas do endpoint com o motivo.Cada conta cadastra até 5 endpoints. Cada um tem a sua URL, o seu filtro de eventos e o seu segredo de assinatura, então dá para mandar cobranças para um serviço e assinaturas para outro.3
Marque os eventos
Um endpoint sem nenhum tipo marcado não recebe nada. Marque só o que você vai processar.
4
Copie o segredo e verifique a assinatura
O segredo aparece no card do endpoint, no formato
whsec_.... Ele é a chave que valida que o POST veio mesmo do Simplo. Veja Verificar a assinatura.Estrutura do payload
O corpo é o próprio evento.data.object carrega um recurso, no mesmo formato em que o endpoint dele devolve — a fatura que chega no seu servidor é byte a byte a fatura de GET /api/v1/invoices/{id}.
id— o identificador do evento. É o mesmo valor do cabeçalhowebhook-ide a sua chave de idempotência.type— o que aconteceu.created— timestamp Unix em segundos do instante do fato.live_mode—trueem produção,falseno sandbox.data.object— o recurso. Recursos relacionados vêm como ID (customer,subscription,payment_intent), e você busca cada um pelo endpoint dele quando precisar.
O corpo é congelado quando o fato acontece. Uma retentativa três dias depois entrega o que era verdade na hora, não o estado atual do recurso. Se você precisa do estado atual, leia o recurso pela API usando o ID que veio em
data.object.id.Eventos disponíveis
O schema de cada um está na referência da API.
Os eventos de assinatura descrevem mudanças diferentes.
subscription.overdue
sai uma vez por episódio de atraso; receber o pagamento e limpar esse atraso não
gera outro evento de ciclo de vida. subscription.reactivated significa que uma
assinatura suspensa voltou para pending; o pagamento seguinte gera
subscription.activated. subscription.period_started sai para cada ciclo
novo, inclusive o primeiro, e uma repetição que encontra o ciclo existente não
gera uma segunda cópia.
Todos carregam a representação pública da assinatura em data.object. Campos
internos usados pela régua de cobrança, como as datas privadas de atraso,
suspensão e cancelamento, não fazem parte desse corpo.
Verificar a assinatura
Toda entrega vai assinada no padrão Standard Webhooks, com três cabeçalhos:{webhook-id}.{webhook-timestamp}.{corpo}, em base64. A chave são os bytes do segredo depois do prefixo whsec_, decodificados de base64.
Como o timestamp entra no conteúdo assinado, uma requisição capturada não pode ser reenviada depois: recuse entregas com mais de 5 minutos de diferença do seu relógio.
Com a gem standardwebhooks
verify cuida da tolerância de tempo e da comparação em tempo constante, e devolve o corpo já decodificado.
Sem a gem
Verifique sobre o corpo cru, exatamente como chegou. Reserializar o JSON muda os bytes e a assinatura deixa de conferir.Rotacionar o segredo
Rotacionar no card do endpoint gera um segredo novo. Pelas 24 horas seguintes, o cabeçalhowebhook-signature traz duas assinaturas, uma por segredo, separadas por espaço.
Aceite a entrega se qualquer uma conferir, e você troca o segredo no seu lado sem perder nenhum evento no meio do deploy. Depois das 24 horas, só o novo assina.
Entrega
Responda 2xx rápido
O Simplo espera até 15 segundos pela resposta, com 5 segundos de timeout de conexão. Só2xx conta como entregue: um 3xx é a sua URL dizendo que mudou de lugar, e é você quem conserta isso no cadastro.
A regra é guardar e enfileirar. Grave o corpo, responda 200, processe em job de background. Processar dentro da request é o que faz uma entrega estourar o timeout num dia de pico.
Retentativa: 10 tentativas em 93 horas
Uma entrega que falha é retentada. São dez tentativas no total, densas no começo, porque a maioria das falhas é um deploy ou um restart, e depois diárias, porque o que sobrevive a seis horas é uma indisponibilidade que precisa de conserto.
Cada tentativa aparece no painel, no card do endpoint, com o código de resposta, a duração e quando é a próxima.
Deduplique pelo webhook-id
Retentativa significa que o mesmo evento pode chegar duas vezes — por exemplo, quando o seu servidor processou o POST mas demorou 16 segundos para responder. O evt_ é sempre o mesmo, então guarde-o com índice único e trate a segunda chegada como sucesso.
invoice.paid pode chegar antes de invoice.created se a primeira entrega tiver falhado. Use created para ordenar, ou leia o recurso pela API quando a ordem importar.
Saúde é informação, não interruptor
Falhas consecutivas marcam o endpoint como instável no painel. Isso não pausa nada: os próximos eventos continuam sendo enviados, e cada um roda a escada inteira de dez tentativas. Um endpoint que passa dias sem aceitar nenhuma entrega é um endereço que não existe mais, e aí sim o Simplo age — sempre avisando antes:- 3 dias sem nenhuma entrega aceita: e-mail de aviso.
- 6 dias: segundo e-mail.
- 7 dias: o endpoint é desativado, e você reativa no painel quando o endereço voltar.
Reconciliar o que não chegou
Os eventos ficam consultáveis por 30 dias, tenham sido entregues ou não.delivery_status responde à pergunta que interessa:
GET /api/v1/events/{id} devolve o mesmo corpo que a entrega carregou, o que permite conferir o que você guardou contra o que o Simplo mandou.
Reenviar
evt_, para os endpoints ativos que assinam aquele tipo agora — inclusive um endpoint cadastrado depois do fato. Cada reenvio abre uma escada nova de dez tentativas.
São até 3 reenvios por evento, a qualquer momento dentro dos 30 dias. O botão também está no painel, na lista de entregas de cada endpoint.
Testar localmente
Exponha o seu servidor local com ngrok ou cloudflared:invoice.paid sai para os seus endpoints com live_mode: false. O botão Testar não substitui isso — ele confere o caminho e a assinatura, e um evento de verdade confere o seu processamento.
