Skip to main content

Como funciona

Quando algo acontece na sua conta — uma compra aprovada, um Pix gerado, uma assinatura cancelada — a Cakto envia uma requisição POST para a URL que você cadastrou. Você cadastra webhooks em Criar Webhook ou pelo Painel Cakto.

Formato da entrega

O corpo tem sempre três campos — secret, event e data. Os dois primeiros são strings; data muda de forma conforme o evento, e é o objeto que carrega a venda. Veja O que vem em data. Exemplo abreviado de purchase_approved. O data real tem 43 campos fixos, mais o bloco do meio de pagamento — aqui estão só os mais usados:
Testar Webhook é a forma mais rápida de ver uma entrega chegar sem esperar uma venda.
O payload de teste é um exemplo fixo, não um retrato da sua conta, e não é igual a uma entrega real em três pontos: ele traz card, boleto, pix e picpay ao mesmo tempo (uma entrega real traz só o bloco do meio de pagamento efetivamente usado), sempre preenche subscription (mesmo em produto sem assinatura) e omite charged_fees, interest e additionalInstallmentInterest. Use-o para validar conectividade e a checagem do secret — para o contrato de campos, use a tabela abaixo.

O que vem em data

data tem duas formas. Todos os eventos de pedido usam a forma de pedido; checkout_abandonment usa uma forma própria, sem nenhum campo de pagamento.
Webhook V2 entrega data como lista. Existe uma segunda versão de webhook, criável apenas pelo Painel (não por Criar Webhook), que usa o mesmo envelope mas manda em data um array: todos os pedidos da mesma cobrança — principal, order bump, upsell e downsell — numa entrega só, e um array de um elemento em checkout_abandonment.Isso importa mesmo para quem só usa a V1: o histórico de entregas devolve os eventos de todos os webhooks da conta, então uma entrega com data em lista pode aparecer ali. Teste o tipo antes de acessar campo (Array.isArray(payload.data)).

Forma de pedido

Lista completa: estes 43 campos vêm sempre, em todos os eventos de pedido.
Dinheiro chega em dois tipos no mesmo payload. baseAmount, amount, fees e offer.price são números JSON. discount, charged_fees, interest e additionalInstallmentInterest são strings ("0.00"). Converta os quatro últimos antes de somar — em JavaScript, 5.0 + "0.00" vira "50.00".
affiliate e parent_order vêm como string vazia quando não existem, não como null.

Bloco do meio de pagamento (condicional)

Quando o pedido já tem uma cobrança criada, uma chave a mais é acrescentada ao data, com o nome do meio de pagamento usado — e o bloco reflete a cobrança mais recente do pedido, não a primeira (um pedido que tentou cartão e caiu para Pix chega com o bloco pix): card, pix, boleto, oxxo, pse, nequi, spei, safetypay, picpay, pagaleve, googlepay, applepay, paypalWallet, openFinanceNubank, mercadopagoWallet. card tem forma fixa — { holderName, lastDigits, brand }. O conteúdo dos outros é o JSON da adquirente e varia por adquirente; os formatos mais comuns são pix: { qrCode, expirationDate } e boleto: { barcode, boletoUrl, expirationDate }.
A chave ausente não vem como null — ela não existe no objeto. Teste presença ("pix" in data, data.pix !== undefined), não valor. Um pedido sem cobrança criada não traz nenhuma dessas chaves.

Forma de abandono de checkout

O evento checkout_abandonment entrega um objeto diferente, com 7 campos:
Aqui não existe id, nem status, nem amount, nem customer — não houve pedido. O dado do comprador vem achatado em customerName / customerEmail / customerCellphone. Um handler que lê data.id para deduplicar quebra neste evento: trate checkout_abandonment num ramo separado, antes do processamento comum.

Dado do cliente dentro de subscription

Dentro do objeto subscription, o customer só vem completo em três casos: o dono do webhook é o produtor do produto, é um coprodutor dele, ou o produto está com “compartilhar contato do cliente com afiliados” ligado — nesse caso o dado completo vai para todo mundo que recebe o evento. Fora isso vem apenas { "name": "..." }. O customer do nível de cima não sofre esse corte: ele vem completo sempre.

Validando a origem

Há duas formas, e as duas usam o mesmo secret, gerado automaticamente quando você cria o webhook. Preferimos a assinatura: ela prova a origem sem depender do segredo que veio no corpo, e ainda detecta payload adulterado.
Nada mudou para quem já valida pelo corpo: o campo secret continua em toda entrega. Os headers são adicionais.

Assinatura no header (recomendado)

Cada entrega leva X-Cakto-Timestamp (Unix time em segundos) e X-Cakto-Signature, no formato v1=<hmac-sha256>. O digest é o HMAC-SHA256, com o seu secret como chave, de:
O corpo tem que ser o byte a byte recebido, antes de qualquer parse — reserializar o JSON muda os bytes e invalida a comparação.
O prefixo v1= versiona o algoritmo. Se um dia houver v2, o header passa a trazer as duas assinaturas separadas por vírgula durante a transição — compare a versão que você conhece e ignore as demais.

Campo secret no corpo

O secret também vai no corpo de toda entrega. Sua aplicação compara o valor recebido com o que armazenou e rejeita a requisição se não bater.
Como o secret trafega no corpo, sua URL de webhook precisa usar HTTPS. Trate o valor como credencial: guarde em variável de ambiente e nunca versione no git.
Validar só pelo corpo tem um custo: o segredo chega em toda entrega e fica em qualquer log, proxy ou ferramenta de replay que registre corpo de requisição. A assinatura no header não tem esse problema — ela prova a origem sem que o segredo precise ser lido do payload.

Retentativas

A entrega é considerada bem-sucedida quando sua aplicação responde com status 2xx. Qualquer outra resposta — ou um timeout — marca a entrega como falha no histórico. O que decide se haverá reenvio automático é o tipo da falha. Retentativa automática acontece quando a entrega falha antes de a sua aplicação responder: erro de rede ou estouro do tempo limite. Nesse caso são até 5 retentativas, com intervalos crescentes a partir do envio original: Depois disso a entrega é encerrada como falha, e você pode reenviá-la manualmente com Reenviar Evento.
Uma resposta não-2xx da sua aplicação não é retentada automaticamente. Se o seu endpoint respondeu — mesmo que com 500 — a Cakto registra a entrega como falha e para ali: devolver erro não faz o evento voltar sozinho. Quem reenvia é você, com Reenviar Evento.A consequência prática: se a sua aplicação pode falhar temporariamente (banco indisponível, fila cheia), não conte com o reenvio automático. Responda 2xx ao receber, enfileire do seu lado, ou acompanhe o histórico de entregas e reenvie o que falhou.
Responda 2xx assim que receber o evento e faça o processamento pesado de forma assíncrona. O tempo limite de resposta é de 8 segundos — passando disso, a Cakto considera timeout e reenvia, mesmo que sua aplicação tenha processado o evento com sucesso.
Seu handler precisa ser idempotente. Por causa das retentativas, o mesmo evento pode chegar mais de uma vez. Use o data.id do pedido como chave de deduplicação e ignore o que já processou. Em checkout_abandonment não existe data.id — deduplique por customerEmail + offer.id + createdAt.

Catálogo de eventos

Compra

Cobrança gerada

Assinatura

Um mesmo webhook pode assinar vários eventos. Os eventos disponíveis para seleção estão no corpo de Criar Webhook.
Fora checkout_abandonment, todos os eventos das tabelas acima entregam a mesma forma de pedido. O que muda entre eles é o status, quais datas vêm preenchidas e qual bloco de meio de pagamento aparece: pix_gerado traz status: "waiting_payment" e o bloco pix; refund traz refundedAt e refund_reason; chargeback traz chargedbackAt; os de assinatura trazem subscription preenchido. Nenhum evento remove campo do objeto.
initiate_checkout aparece na lista de eventos da API (é um tipo de evento da plataforma), mas não é entregue por webhook. Ele alimenta apenas os pixels de rastreamento, e dispara no carregamento do checkout — quando ainda não existe pedido nem cobrança, ou seja, não há data possível. Não assine esse evento esperando entrega.

Acompanhando entregas

Histórico de Eventos

Consulte o que foi enviado, o status e o tempo de resposta

Reenviar Evento

Dispare de novo um evento que falhou

Testar Webhook

Envie um payload de exemplo para validar sua integração

Criar Webhook

Cadastre uma URL e selecione os eventos