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.
O que seu endpoint responde pode continuar a conversa
Quando o AI employee do tipo JSON despacha para a sua URL —ou uma açãosend_json, que envia a você uma requisição fixa sem que um
modelo a redija—, o que seu servidor responder pode ser lido por outro funcionário AI para
continuar escrevendo ao cliente, se a equipe do negócio configurou esse processamento: no
funcionário JSON pelo painel, ou na própria ação. Não é automático: vem desligado. Uma vez
configurado no funcionário JSON, não importa como ele foi disparado: pelo painel, como ação
de outro funcionário AI, dentro de uma campanha ou por esta API
(run-ai, ai-assistance).
Isso não muda o contrato desta API: mesmos shapes, mesmos status, mesmos códigos de erro,
e sua resposta continua sendo registrada como hoje. A única diferença é o que nós fazemos com
ela. Ainda assim, há cinco coisas que vale saber antes de decidir o que você devolve:
-
O corpo da sua resposta é mostrado a um modelo. Responda só com aquilo que você aceita
que o cliente venha a saber: nem credenciais, nem dados de outro cliente, nem dumps internos.
Se for longo, é truncado. Antes de mostrá-lo ocultamos os valores dos campos com nome de
credencial (
token,password,api_key…), os números de 18 dígitos —mesmo separados por espaços ou hífens— e a parte/pay/…dos links, então se a sua resposta trouxer uma CLABE ou um link com/pay/…, o AI não vai conseguir repassá-los ao cliente. Esse filtro é uma rede de proteção, não uma garantia: ele não reconhece todos os nomes de campo nem os links de outras gateways, então não envie credenciais nem dados bancários contando com ele. -
Só é lida se você responder
2xxe com corpo, e mesmo assim não é garantido. Um erro ou uma resposta vazia não continuam nada, e esse despacho não é repetido se você responder4xxou5xx. Ele só se repete uma vez se você não responder em 30 segundos ou a conexão falhar, então pode chegar duas vezes e sem chave de idempotência: este despacho não levawebhook-idnem assinatura, então a deduplicação fica por sua conta. Os reenvios, o timeout e owebhook-idde Entrega e reenvios são apenas dos webhooks de eventos de pagamento. Continua, sim, mesmo que naquele momento uma pessoa esteja atendendo a conversa ou ela esteja pendente. Não há um prazo garantido entre a sua resposta e a mensagem ao cliente. -
A cadeia tem um teto, e ele depende de onde saiu o despacho. Se a continuação despachar de
novo para a sua URL, essa resposta também pode continuar a conversa, até o teto:
- Até duas continuações quando o despacho sai do que abre o turno: o funcionário JSON que
roda por conta própria —o atribuído, ou um disparado diretamente pelo painel, por uma campanha ou
por esta API—, uma ação
send_jsonavulsa em um grupo de ações, ou o action set do funcionário que responde a uma mensagem do cliente, a uma mensagem agendada ou ao botão «Executar» sobre o atribuído (com umsend_jsondele ou um funcionário JSON que esse set dispara). - Uma só quando despacha o action set de um funcionário que outro disparou: outro action set,
esta API (
run_ai,ai_assistance,assign_ai_employeecomrun: trueou Smart Actions), uma campanha, ou «Executar» sobre um funcionário que não é o atribuído. Também fica em uma o funcionário JSON que esse set dispara.
- Até duas continuações quando o despacho sai do que abre o turno: o funcionário JSON que
roda por conta própria —o atribuído, ou um disparado diretamente pelo painel, por uma campanha ou
por esta API—, uma ação
-
Uma campanha despacha para você uma vez por destinatário. Se a equipe do negócio coloca um
send_jsonou um funcionário JSON em uma campanha, chega a você uma requisição por destinatário, e com o processamento configurado cada resposta pode continuar a sua conversa. Umsend_jsonque vai antes da primeira ação de AI da campanha sai no ritmo do envio. Se vai depois, sai de uma fila que limita quantos começam ao mesmo tempo por negócio, mas não quantos continuam rodando: um que demora pode liberar o seu lugar antes de terminar. Não há um ritmo garantido. Dimensione o seu endpoint pelo tamanho da audiência, não pelo de uma conversa. -
Não se aplica aos despachos que levam o campo do pagamento, mesmo que não traga nenhum
pagamento (um array vazio, ou um objeto com
datavazio). Ali a resposta não é lida, por design: um endpoint que ecoa a requisição devolveria dados bancários, e o corte não distingue um campo sem pagamentos de um com pagamentos.
Disponibilidade: está sendo habilitado por etapas no nível da plataforma, não conta a
conta.
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
Id único da entrega. Estável entre reenvios de um mesmo evento — use-o para
deduplicar (ver Entrega).
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.
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.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.
5
Um aviso pode chegar atrasado
Se uma entrega não foi gerada por uma falha do nosso lado, podemos recuperá-la,
normalmente em cerca de 15 minutos (até 3 dias depois do evento). O aviso
recuperado traz em
event_at a hora original do evento, não a de envio: use
event_at para datar o pagamento, nunca a hora em que recebe o webhook. Ao
reativar uma assinatura pausada ou desabilitada, ou ao adicionar um evento a
ela, você pode receber de uma vez avisos de pagamentos de até 3 dias atrás. O
objeto payment traz o estado do pagamento no momento do envio, não o do
evento: um payment.registered recuperado pode chegar com
status: "credited" ou "failed". Não é uma nova cobrança: como em «Não
assuma ordem», use data.payment.uuid + status como a verdade.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.