POST assinado para a URL que você registrar, com os detalhes do pagamento.
Estes webhooks são de saída (1to1 → seu servidor). Você não os chama: você os
recebe. Cada entrega é assinada (padrão Standard Webhooks)
para que você verifique que veio de nós e não foi alterada.
Configurar um endpoint
No dashboard, em Configurações → API e Conexões, adicione a URL do seu sistema e escolha os eventos que quer receber. Ao criar, você recebe um signing secret (whsec_…) mostrado uma única vez — guarde-o como um segredo: é a chave com
que você verifica cada webhook. Você pode registrar até 10 endpoints por negócio.
Catálogo de eventos
payment.failed cobre apenas os pagamentos manuais marcados como falhos pela
sua equipe. As falhas de pagamentos online (cartão recusado, sessão expirada)
não emitem webhook.O payload
CadaPOST leva um corpo JSON com esta forma. O exemplo é um pagamento manual
creditado:
event_at é o momento em que o evento do pagamento ocorreu (para
payment.failed, a hora da falha). É diferente do header webhook-timestamp,
que é o instante de envio usado para a assinatura anti-replay.provider: "manual"):
bank_account (a etiqueta da conta em que o dinheiro entrou, ex.
"BANORTE0876"), bank_datetime (data/hora no banco, opcional) e files
(comprovantes enviados). Em pagamentos online ficam null / []. Em
pagamentos manuais, transaction_id é o ID de banco capturado pelo operador
ao creditar (null se não foi capturado); em pagamentos online é o id do
provedor. Em todo pagamento online creditado receipt_url traz sempre uma
URL do nosso domínio (https://…/pay/receipt/<uuid>), nunca o recibo
hospedado do provedor; em pagamentos manuais é null. Essa página não exige
autenticação e não tem vencimento por tempo (ao contrário dos links do
provedor); deixa de servir se o pagamento for excluído ou deixar de estar
creditado. O PDF do recibo é baixado adicionando o sufixo /pdf a essa mesma
URL.
O payload cresce com o tempo: podemos adicionar campos novos ao webhook sem aviso
prévio, e isso não conta como mudança incompatível. Os que já estão documentados aqui não
são removidos nem renomeados sem anunciar antes, e uma mudança incompatível chega como um
tipo de evento novo, nunca alterando um existente.Por isso seu parser deve ignorar o que não reconhecer —tanto chaves quanto tipos de
evento— em vez de falhar: não configure
additionalProperties: false na validação de
schema, nem use structs que rejeitem campos desconhecidos. Uma entrega que seu endpoint
rejeita é reenviada por ~3 dias e pode acabar desabilitando a assinatura.Isso se aplica ao shape do payload. Os limites de tamanho e o conteúdo dos campos de
texto livre podem mudar: se você dimensionou seu armazenamento pelos topos acima, revise
este guia antes de assumir que continuam os mesmos.description e custom_fields: o contexto que sua equipe escreve
Dois campos que descrevem do que se trata o pagamento, vindos do que seu próprio
negócio captura:
descriptioné a nota do pagamento. Quem o registra a escreve no painel, ou seu AI employee a redige se você configurou um guia para isso. É prosa livre:nullquando ninguém escreveu nada.custom_fieldssão os campos personalizados que você definiu na sua conta —«Tipo de pagamento», «Pedido», os que precisar— com o valor capturado naquele pagamento. Enquanto o campo estiver em liberação por etapas a chave pode não vir; quando vem é sempre um objeto:{}se nenhum foi capturado.
custom_fields são os nomes que você deu, literalmente, então renomear um
campo na sua conta muda a chave nos pagamentos seguintes. Se for mapeá-los para o seu
sistema, mapeie pelo nome que usa hoje e revise esse mapeamento ao renomear.
Quando as duas chaves vêm, hoje quem as preenche é só o fluxo de pagamentos manuais: em
pagamentos com cartão ou link chegam como null e {}.
Disponibilidade: esses dois campos estão sendo habilitados por etapas no nível da
plataforma, não conta por conta. Se você ainda não os vê nas suas entregas é porque ainda
não foram publicados — fale com a gente e avisamos quando forem.
Se você recebe eventos por duas vias, deduplique por payment.uuid
Além destes webhooks, um AI employee do tipo JSON pode despachar o pagamento para uma URL
sua como parte do que extrai da conversa. Se usar as duas vias, o mesmo pagamento vai
chegar duas vezes, com o mesmo objeto payment em ambas.
Deduplique por data.payment.uuid, que é estável entre as duas. Não use o folio:
pagamentos com cartão e link nem sempre trazem um.
Se você também usa um AI employee do tipo JSON que anexa o pagamento, o que recebe dentro do campo dele é este mesmo objeto data completo —payment, contact_info e conversation_info—, então o código que já escreveu para processá-lo serve igual. A única coisa que não leva é o envelope (id, type, event_at), porque esse descreve esta entrega em particular.
Esse campo tem duas formas, e o AI employee escolhe qual conforme o que escreve nele.
- Se não escreve nada ali, ou escreve um texto, você recebe o array direto:
"pagamento": [ { "payment": …, "contact_info": …, "conversation_info": … } ] - Se escreve um objeto com a leitura dele, o pacote fica aninhado sob
data:"pagamento": { "tipo": "entrada", "data": [ { "payment": … } ] }
data dele. Um parser escrito só contra a primeira forma deixa de encontrar o
pagamento no dia em que o AI escrever um objeto ali.Essa chave data não é o objeto data do webhook: é o array desses objetos. E a mesma
regra vale para os campos de arquivos.Disponibilidade: esta via está sendo habilitada por etapas no nível da plataforma, não
conta por conta. Fale com a gente se quiser que avisemos quando for publicada.
fail_reason: texto livre escrito por uma pessoa
Em payment.failed, fail_reason traz o motivo que sua equipe escreveu à mão
ao marcar o pagamento como falho — por que não conseguiram verificá-lo. É prosa no
idioma de quem escreveu, não um código de uma lista fechada: não o use como chave
de uma tabela de traduções nem tente fazer parsing dele. O painel o exige antes de
deixar marcar o pagamento, mas faça null-check mesmo assim: o endpoint ainda o
aceita vazio enquanto durar a implantação desta função. Em qualquer outro evento é
null.
Como payment.failed cobre apenas pagamentos manuais (as falhas de gateway
não emitem webhook), você nunca vai receber um código de provedor por este campo.
Headers de cada entrega
string
Id único da entrega. Estável entre reenvios de um mesmo evento — use-o para
deduplicar (ver Entrega).
integer
Instante de envio (segundos Unix). Recomputado a cada reenvio e faz parte da
assinatura. Rejeite os que estiverem fora de uma janela razoável (±5 min) para
se proteger de replays.
string
Uma ou mais assinaturas
v1,<base64> separadas por espaço (haverá duas
durante uma rotação de secret). A entrega é válida se alguma coincidir.string
O tipo de evento (
payment.credited, payment.failed, …). Coincide com type
do corpo.string
Sempre
1to1-Webhooks/1.0.Verificar a assinatura
A assinatura é um HMAC-SHA256 do conteúdo{webhook-id}.{webhook-timestamp}.{body},
onde body é o corpo cru exato que você recebeu (não o re-serialize). A chave
é seu whsec_… com o prefixo removido e o resto decodificado de base64.
A forma mais simples é a biblioteca oficial do Standard Webhooks, que cuida da
rotação e da comparação em tempo constante por você:
Entrega e reenvios
1
Responda 2xx rápido
Responda com qualquer
2xx assim que receber o webhook. Se demorar mais de
10 segundos ou responder outro código, tratamos como falha e reenviamos.2
Deduplique por webhook-id
A entrega é at-least-once: um mesmo evento pode chegar mais de uma vez (um
reenvio após um timeout, por exemplo). O
webhook-id é estável entre
reenvios — guarde-o e descarte os repetidos.3
Não assuma ordem
Os eventos não chegam garantidamente em ordem. Use
data.payment.uuid +
status como a verdade, não a ordem de chegada: um payment.registered que
chegue atrasado não deve sobrescrever um payment.credited que você já
processou.4
Reenvios ~3 dias
Um endpoint fora do ar recebe reenvios com espaçamento crescente por ~3 dias.
Se continuar falhando, a assinatura é desabilitada automaticamente (após 5
falhas consecutivas esgotadas) — você a reativa pelo dashboard.
Rotacionar o secret
Pelo dashboard você pode rotacionar o signing secret quando quiser. Durante um período de graça de 24 horas, cada webhook é assinado com o secret novo e o anterior ao mesmo tempo (duas assinaturas no header). Assim você atualiza seu sistema sem perder nem rejeitar eventos: valide contra qualquer uma das duas; quando termina o período de graça de 24 h, o secret anterior deixa de assinar automaticamente.Comprovantes
Em um pagamento manual,files[] lista os comprovantes enviados com referências
estáveis — uuid, name e mime_type — mas sem URL de download: o payload é
um snapshot reenviado por dias, e uma URL assinada expiraria no caminho. Para
baixar o arquivo, peça um link assinado com a mesma API key da sua integração —
sem configuração adicional. O {paymentUuid} do path é data.payment.uuid, e
o {fileUuid} é o uuid do comprovante dentro de data.payment.files[],
ambos vindos do próprio evento:
download_url vence em 30 minutos (expires_at); se expirar antes de
você usá-lo, repita o mesmo GET para obter um novo.
O endpoint responde 404 em dois casos, ambos terminais — veja o detalhe no
catálogo de erros: PAYMENT_NOT_FOUND se o paymentUuid não
resolve para sua API key, e FILE_NOT_FOUND se o fileUuid não é um
comprovante confirmado desse pagamento.
Próximos passos
Autenticação
Como sua integração se autentica com a API key.
Erros
O contrato de erros da API pública.