Receber e validar webhooks
Valide a assinatura dos webhooks e processe notificações com segurança, inclusive quando houver reentrega.
Prepare um endpoint HTTPS que aceite POST com Content-Type: application/json.
Use a porta padrão 443 e um domínio com DNS que resolva para um IPv4 público.
Ele deve receber chamadas entre sistemas, sem login interativo, CAPTCHA ou
redirecionamento. Configure o destino e os eventos em
Webhooks e permita os
cinco IPs oficiais quando houver restrição
de origem.
Corpo do evento
Cada requisição identifica o evento, o ambiente e o recurso alterado:
{
"id": "d377b4c5-5678-4c42-8ea3-8f823156948e",
"type": "charge.paid",
"created_at": "2026-10-15T12:30:00.000Z",
"environment": "sandbox",
"data": {
"object": "charge",
"id": "ch_4ZxYp8Q2mV7nR6sT5uW3aB"
}
}| Campo | Significado |
|---|---|
id | UUID do evento, preservado nas reentregas e nos destinos que recebem o mesmo evento. |
type | Nome do evento, como charge.paid, refund.completed ou webhook.test. |
created_at | Data do evento em UTC, no formato ISO 8601. Não representa o horário de cada tentativa. |
environment | sandbox ou production, conforme o ambiente do recurso. |
data.object | Tipo do recurso: charge para cobrança, refund para estorno ou webhook para teste. |
data.id | Identificador público do recurso: cobrança ch_, estorno rf_ ou webhook whk_. |
O corpo mantém os mesmos bytes em todas as tentativas. Use data.id para
consultar o estado atual em Cobranças ou
Estornos, com uma chave da mesma empresa e ambiente.
O evento informa uma mudança; seu processamento não deve presumir que esse
continua sendo o estado mais recente do recurso.
data identifica o recurso e não contém o cadastro completo da cobrança
ou do estorno. created_at usa ISO 8601 neste corpo; as datas retornadas
pelas consultas de entregas e tentativas usam segundos Unix UTC.
Cabeçalhos de assinatura
| Cabeçalho | Significado |
|---|---|
webhook-id | UUID da entrega para aquele destino. Permanece igual nas novas tentativas. |
webhook-timestamp | Horário da tentativa em segundos Unix UTC. |
webhook-signature | Uma ou mais assinaturas v1=, com 64 caracteres hexadecimais cada, separadas por vírgula. |
O webhook-id difere do id do corpo: um evento pode ser entregue a mais de
um destino ou reenviado manualmente. Cada entrega tem seu próprio
webhook-id. Novas tentativas da mesma entrega preservam esse ID e o corpo
original, mas podem ter outro timestamp e outra assinatura.
Durante o período de transição escolhido pela API,
o mesmo cabeçalho contém primeiro a assinatura do segredo novo e depois a do
anterior: v1=<assinatura nova>,v1=<assinatura anterior>. O receptor deve
aceitar a requisição quando qualquer assinatura v1 corresponder ao segredo
configurado. Quando o prazo termina ou uma rotação imediata encerra a
transição, os novos envios incluem somente a assinatura do segredo atual.
Validar a assinatura
- Capture o corpo HTTP como bytes, antes de interpretar o JSON.
- Leia os três cabeçalhos de assinatura.
- Retire o prefixo
whsec_do segredo do cadastro e decodifique a parte restante em Base62, nesta ordem: dígitos de0a9, letras deAaZe letras deaaz. Represente o número em 32 bytes little-endian, completando com zeros à direita quando necessário. Esses bytes são a chave do HMAC. - Monte a mensagem com os bytes UTF-8 de
webhook-id + "." + webhook-timestamp + ".", seguidos pelos bytes exatos do corpo recebido. - Separe as assinaturas do cabeçalho pela vírgula e selecione os valores
v1. Calcule HMAC-SHA256 e compare o resultado com cada assinatura usando uma comparação em tempo constante. Aceite a requisição se houver uma correspondência.
Não use o texto whsec_... diretamente como chave do HMAC. Não serialize o
JSON novamente: espaços, quebras de linha e ordem das propriedades fazem
parte da assinatura.
Configure o framework para preservar o corpo original nessa rota. Um objeto já convertido de JSON para a linguagem da aplicação não serve para conferir a assinatura. Rejeite a requisição se a assinatura ou o timestamp não forem válidos, sem executar seus efeitos.
Recomendamos aceitar timestamps com diferença de até cinco minutos em relação ao relógio do receptor, mantendo esse relógio sincronizado. Essa verificação reduz o uso de requisições antigas. A deduplicação da entrega continua necessária dentro dessa janela.
A tolerância se aplica ao webhook-timestamp da tentativa, não ao
created_at do corpo. Novas tentativas e reenvios recebem timestamp e
assinatura atuais, enquanto a data do evento original permanece no corpo.
Exemplo em Node.js
O exemplo recebe o segredo privado, os cabeçalhos e um Buffer com o corpo
original. Ele aceita tanto um único v1 quanto as assinaturas do período de
transição, sem depender da posição da assinatura válida. Não registra esses
valores nem interpreta o JSON antes da validação.
import { createHmac, timingSafeEqual } from 'node:crypto';
const alphabet =
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
function decodeWebhookSecret(secret) {
if (!/^whsec_[0-9A-Za-z]{1,43}$/.test(secret)) {
throw new Error('Invalid webhook secret');
}
const encoded = secret.slice('whsec_'.length);
if (encoded.length > 1 && encoded.startsWith('0')) {
throw new Error('Invalid webhook secret');
}
let value = 0n;
for (const character of encoded) {
value = value * 62n + BigInt(alphabet.indexOf(character));
}
const key = Buffer.alloc(32);
for (let index = 0; index < key.length; index += 1) {
key[index] = Number(value & 255n);
value >>= 8n;
}
if (value !== 0n) throw new Error('Invalid webhook secret');
return key;
}
export function verifyWebhook(
secret,
headers,
rawBody,
nowSeconds = Date.now() / 1000,
) {
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signature = headers['webhook-signature'];
if (
!Buffer.isBuffer(rawBody) ||
typeof id !== 'string' ||
!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
id,
) ||
typeof timestamp !== 'string' ||
!/^\d{1,12}$/.test(timestamp) ||
!Number.isFinite(nowSeconds) ||
Math.abs(nowSeconds - Number(timestamp)) > 300 ||
typeof signature !== 'string'
) {
return false;
}
const signatures = signature
.split(',')
.map((entry) => entry.trim())
.filter((entry) => /^v1=[0-9a-f]{64}$/i.test(entry));
if (signatures.length === 0) return false;
const key = decodeWebhookSecret(secret);
try {
const expected = createHmac('sha256', key)
.update(`${id}.${timestamp}.`, 'utf8')
.update(rawBody)
.digest();
return signatures.some((entry) => {
const received = Buffer.from(entry.slice('v1='.length), 'hex');
return timingSafeEqual(expected, received);
});
} finally {
key.fill(0);
}
}Após uma assinatura válida, interprete o JSON, confira o ambiente e registre a entrega antes de responder. Guarde o segredo no servidor da integração, com acesso restrito. Nunca o inclua em URLs, código de navegador, capturas de tela ou mensagens de suporte.
Confirmar o recebimento
Responda com um status HTTP 2xx somente depois de aceitar o evento em um
armazenamento durável do seu sistema. Encaminhe o processamento demorado para
uma fila e execute-o de forma assíncrona. A resposta de sucesso confirma que
o receptor aceitou a entrega; não confirma uma nova transação financeira.
O receptor deve seguir esta sequência:
- Valide a assinatura com o corpo original e confira o ambiente do evento.
- Registre o
iddo evento e o trabalho que será processado de forma durável. Faça essa aceitação impedir duplicatas mesmo quando duas requisições chegarem ao mesmo tempo. - Se o evento já foi aceito, devolva
2xxsem registrar o mesmo trabalho novamente. Caso contrário, confirme o recebimento depois de salvar. - Processe o trabalho aceito e controle sua conclusão separadamente. Se esse processamento falhar após a resposta, seu sistema precisa recuperá-lo: a Raims já recebeu a confirmação de entrega.
Se não conseguir guardar o evento, não confirme com 2xx. O processamento
posterior também deve tolerar repetições sem duplicar seus efeitos.
O timeout de cada envio HTTP de webhook é de 5 segundos no total. Valide a assinatura,
registre o evento com segurança e devolva 2xx dentro desse prazo, sem esperar
o processamento demorado terminar. As até 72 horas de Produção são a janela
para novas tentativas; uma requisição não fica aberta durante esse período.
A Raims não segue redirecionamentos; cadastre diretamente a URL HTTPS final. Configure o certificado com uma cadeia pública válida.
O detalhe de cada tentativa nos logs pode mostrar o corpo da resposta do endpoint, limitado a 8 KiB, e o IP de destino usado naquela conexão. Respostas capturadas parcialmente têm um aviso. Conteúdo não registrado difere de corpo vazio confirmado; registros antigos podem não conter essas informações. O corpo ajuda a explicar erros devolvidos pela integração, mas não deve conter segredos ou dados pessoais desnecessários. Use o guia de diagnóstico para relacionar o resultado da tentativa à correção no receptor.
Tratar reentregas e ordem
Uma entrega pode chegar mais de uma vez, inclusive quando o receptor já
aceitou o evento e a resposta se perdeu. Registre webhook-id para reconhecer
novas tentativas da mesma entrega e use o id do corpo para impedir que um
reenvio manual do mesmo evento repita seu efeito. Confirme também as duplicatas
já aceitas com 2xx.
Não dependa da ordem de chegada. Eventos de recursos diferentes ou novas
tentativas podem chegar em outra sequência. Consulte o recurso pela API
quando precisar decidir com base no estado atual. Se destinos diferentes
alimentarem a mesma rotina, o id do corpo identifica o mesmo evento entre
eles.
Reenvio manual de eventos
O Dashboard permite solicitar o reenvio de um evento por até 15 dias após sua criação, quando o payload está disponível, o webhook está ativo e o tipo de evento continua selecionado no cadastro. A ação exige Webhooks, Ver logs de eventos e Reenviar eventos de webhooks.
Na API, use Reenviar um evento para o mesmo
webhook de uma entrega anterior. Para escolher o destino pelo evento, use
Enviar um evento para um webhook, inclusive
quando esse destino ainda não tiver uma entrega. Os dois caminhos exigem
um evento de até 15 dias, seu corpo disponível e um webhook ativo que ainda
selecione esse tipo de evento, da mesma empresa e ambiente da chave. A consulta e o reenvio pela API usam a
autenticação da integração. Eventos técnicos webhook.test não aceitam
reenvio; solicite outro teste.
Para recuperar vários eventos, envie uma lista de entregas ou use Reenviar selecionados nos logs do Dashboard. Cada item continua sujeito às mesmas condições do reenvio individual. Confira os resultados aceitos e recusados antes de repetir um pedido.
O reenvio cria uma entrega com outro webhook-id e preserva os bytes do
corpo original, incluindo id e created_at. A URL e o segredo usados são
os atuais do webhook. Se houver uma transição de segredo ativa, valem as
mesmas regras de assinatura dos demais envios.
O pedido não cancela novas tentativas automáticas da entrega anterior.
Com a fila pausada, a nova entrega também aguarda a retomada e seu próprio
prazo continua contando.
Prepare o receptor para reconhecer o mesmo id do corpo em entregas
diferentes. Solicitar um reenvio não repete a operação financeira que gerou
o evento nem confirma seu recebimento pelo endpoint.
A janela de novas tentativas dessa entrega começa quando o pedido de reenvio
é aceito: até 72 horas em Produção ou até quatro horas no Sandbox, com no
máximo três novas tentativas após a inicial no Sandbox. Em Produção, os
intervalos aumentam exponencialmente e variam entre tentativas, sem uma
quantidade fixa dentro do prazo. O limite para solicitar outro reenvio
continua sendo quinze dias após a criação do evento original; reenviar não
renova esse limite nem altera o created_at do corpo.
Pausa e antecipação de tentativas
Antes de uma manutenção, pause a fila do webhook. Entregas pendentes e novos eventos selecionados aguardam a retomada dentro de seus prazos originais. A pausa não interrompe uma chamada HTTP já autorizada nem congela a janela de 72 horas de Produção ou quatro horas do Sandbox.
Você pode solicitar um teste técnico enquanto a fila estiver pausada. Depois de conferir o receptor, retome a fila para permitir os envios ainda válidos. A retomada não recupera entregas cujo prazo acabou.
Quando a fila está liberada e uma entrega aguarda uma nova tentativa, use Antecipar uma tentativa se o receptor já foi corrigido. Essa ação mantém a mesma entrega e os limites originais. O aceite do pedido não confirma que o envio ocorreu; consulte seu andamento. Para criar outra entrega de um evento elegível, use o reenvio manual.
Falhas e novas tentativas de webhook
Uma resposta HTTP 2xx conclui a entrega. Falhas de conexão, timeout e os
demais códigos HTTP permitem novas tentativas dentro dos limites do ambiente.
Isso inclui 3xx, 400, 401, 403, 404, 429 e 5xx. A Raims não
segue redirecionamentos: configure diretamente a URL HTTPS final.
O evento técnico webhook.test tem uma tentativa HTTP e prazo de até cinco
minutos para o envio. As regras de novas tentativas abaixo se aplicam aos
eventos de cobrança e estorno.
Em Produção, a Raims repete a entrega por até 72 horas a partir do evento. Os intervalos aumentam exponencialmente e variam entre as tentativas, até o limite de uma hora. Não há uma quantidade fixa de tentativas dentro desse prazo.
Em Sandbox, há até três novas tentativas após a primeira, distribuídas ao longo de poucas horas, dentro de uma janela de até quatro horas a partir do evento. Use esse ambiente para conferir como seu receptor lida com duplicatas e falhas.
Retry-After pode indicar uma espera mínima em segundos ou uma data HTTP,
limitada a uma hora. O cabeçalho não prolonga a janela de entrega nem aumenta
a quantidade de tentativas do Sandbox. Confira endereço, assinatura e regras
de firewall ao investigar uma falha; não dependa de um horário exato para a
próxima tentativa.
Alertas de falhas
As falhas não pausam a fila nem desativam o webhook automaticamente. Cada entrega continua seguindo sua política de novas tentativas e seu prazo. Depois que uma entrega esgota os limites, eventos novos continuam gerando suas próprias entregas enquanto o cadastro estiver ativo.
A Raims avisa no Dashboard ao confirmar a quinta e a décima falhas de um
mesmo episódio. Cada alerta é enviado uma vez por episódio. Uma resposta
HTTP 2xx encerra esse episódio; novas falhas podem iniciar outro. Os alertas
não interrompem as novas tentativas nem ampliam seus prazos.
O envio desses alertas por e-mail depende da preferência Falha no webhook. Os destinatários são os e-mails validados da própria conta. Esses e-mails são agrupados por empresa, ambiente e destinatário. O primeiro envio é imediato. Se houver novos avisos, os intervalos seguintes são de 2, 5, 10, 15, 20, 25 e 30 minutos, contados a partir do envio anterior. Depois disso, o intervalo permanece em 30 minutos enquanto chegarem avisos. Após 30 minutos sem novos avisos e sem resumos aguardando envio, o próximo aviso reinicia a sequência, respeitando o intervalo desde o último envio. Não são criados e-mails vazios ou periódicos apenas pela passagem do tempo.
Cada e-mail apresenta até vinte webhooks, a contagem total e um link para consultar os demais no Dashboard.
Os avisos individuais continuam disponíveis no Dashboard. Consulte o estado atual do cadastro e os logs para investigar uma falha; um resumo por e-mail pode incluir alertas anteriores à recuperação do endpoint. As confirmações de ações manuais, como editar ou desativar um webhook, não entram nesse agrupamento.
Corrija o receptor e acompanhe as próximas tentativas. Se você desativou o webhook manualmente, reative-o para permitir novos envios. A reativação inicia um novo episódio de falhas e preserva o cadastro, o segredo e os eventos selecionados. Rotacionar o segredo não zera a contagem nem reativa um webhook desativado. Editar a URL ou os eventos também preserva o estado do cadastro, a contagem de falhas e os prazos de novas tentativas.
Desativar o cadastro impede novos envios. Se a próxima tentativa de uma entrega pendente encontrar o webhook desativado, essa entrega é encerrada. Reativá-lo antes dessa tentativa permite que ela continue dentro dos limites originais. A reativação não reenvia eventos anteriores nem recupera entregas já encerradas automaticamente. Quando elegível, um evento pode receber uma nova entrega por reenvio manual. Requisições em andamento ainda podem terminar.
Testar o recebimento pela API
Depois de preparar o receptor, use Enviar um evento de
teste para o webhook ativo. O evento chega pela mesma
URL e com a mesma validação de assinatura dos demais envios, com
type: webhook.test, data.object: webhook e o identificador do cadastro
em data.id.
O teste verifica conectividade e assinatura. Ele não altera cobranças, pagamentos, estornos, saldo ou a contagem de falhas do webhook. Consulte a entrega retornada para conferir o resultado. O registro pode levar alguns instantes para aparecer.
O prazo de até cinco minutos inclui a espera até que o teste possa ser
enviado. A chamada HTTP ao receptor continua limitada a cinco segundos.
Uma resposta 2xx ao teste não comprova que sua aplicação trata eventos
financeiros; confira esse fluxo com as simulações do Sandbox.
Cada nova requisição cria um teste. Se houver falha, corrija a integração antes de solicitar outro; o evento técnico não tem repetição automática após a tentativa HTTP.
Testar webhooks no Sandbox
Cadastre um destino no Sandbox e selecione os eventos desejados. Crie uma cobrança e use Simulação para confirmar um pagamento ou testar falhas e estornos. As mudanças confirmadas geram os eventos correspondentes do Sandbox para esse destino, sem movimentar dinheiro real.
As requisições de webhook são reais e usam os mesmos IPs oficiais de
Produção. O corpo informa environment: sandbox. Mantenha o receptor e os
efeitos desse teste separados dos dados de produção do seu sistema.