Visão geral
Webhook é o Faturei Hoje avisando o seu sistema quando alguma coisa acontece na empresa: um cliente cadastrado, uma venda paga, uma OS concluída. Em vez de você perguntar à API de tempos em tempos, a API manda um POST com o evento para um endereço seu, o endpoint.
Como o evento chega
Seção intitulada “Como o evento chega”- Alguém grava alguma coisa: pelo painel, pela API, pelo WhatsApp, por uma automação ou por uma rotina automática.
- Na mesma gravação, o evento é registrado com o objeto do jeito que o
GETdaquele recurso devolveria naquele instante, com as mesmas regras de campos. - O evento vira uma entrega para cada endpoint ligado e inscrito nele.
- A entrega é assinada e enviada. Se falhar, ela é tentada de novo por uma agenda de cerca de 3 dias. Veja Entregas e novas tentativas.
O evento só é registrado quando a empresa tem, naquele momento, ao menos um endpoint ligado e inscrito naquele tipo de evento. O que acontece enquanto nenhum endpoint escuta não vira evento, nem depois.
Criar um endpoint
Seção intitulada “Criar um endpoint”Pela API, com POST /public/v1/webhook_endpoints:
curl -X POST "https://api.fatureihoje.com/public/v1/webhook_endpoints" \ -H "Authorization: Bearer $FH_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 0f5d3c1e-7a2b-4e8f-9c6d-1b2a3c4d5e6f" \ -d '{ "url": "https://erp.suaempresa.com.br/webhooks/faturei-hoje", "description": "ERP", "events": ["sale.created", "sale.paid", "client.created"] }'A resposta traz o endpoint e o campo secret, que começa com whsec_. Pela API, o segredo só aparece aqui e na rotação: guarde-o no seu servidor, é com ele que você verifica a assinatura. Repetir a mesma chamada com a mesma Idempotency-Key devolve o endpoint sem o segredo. No painel, dono e administradores podem revelar o segredo de novo, confirmando a senha.
eventsé a lista de nomes do Catálogo de eventos, ou["*"]para todos, inclusive os que entrarem no catálogo depois.*vai sozinho: junto de outro nome, a resposta é 422.- Gerir webhooks é de dono e administrador, pelo painel (em Configurações → API → Webhooks) ou por uma chave de API de um membro com esse papel. No painel, mudar a URL ou os eventos pede a senha. As rotas de leitura também exigem o papel, porque a lista de endpoints diz para onde os dados da empresa vão.
- Receber um evento é ler o objeto dele. Por isso, para se inscrever, a chave precisa de
<modulo>:readde cada módulo dos eventos escolhidos (com*, de todos). Sem isso, a resposta é 403. - Cada endpoint tem o seu segredo. Dois endpoints nunca dividem segredo.
- Dono e administradores recebem um e-mail a cada endpoint criado.
Para testar a conexão, POST /public/v1/webhook_endpoints/{id}/ping manda um evento ping de verdade só para aquele endpoint, pelo mesmo caminho de qualquer evento (assinatura, novas tentativas, log de entregas). Não é ambiente de teste: é só uma conferência de que o endereço recebe e verifica. Endpoint desligado responde 409 conflict.
As rotas completas, campo a campo, estão na Referência.
Para onde a entrega pode ir
Seção intitulada “Para onde a entrega pode ir”A URL passa por uma conferência no cadastro, e a mesma conferência roda de novo em toda tentativa de envio, porque o dono do domínio pode trocar o DNS depois do cadastro.
- Só
https://. Endereçohttp://é recusado. - Qualquer porta de 1 a 65535.
- Sem usuário e senha na URL (
https://usuario:senha@...): é recusado, porque ficaria guardado e apareceria na leitura do endpoint. - O nome precisa resolver para endereço público na internet.
localhost, nome sem ponto, nomes terminados em.local,.internal,.lane parecidos, IP privado, reservado ou de metadados de nuvem são recusados. Todos os registros A e AAAA do nome são conferidos: basta um interno para recusar. - O certificado TLS precisa ser válido. Certificado autoassinado ou vencido faz a entrega falhar.
- Redirecionamento não é seguido. Um
3xxconta como falha.
No cadastro, a URL recusada responde 422 com param: "url". No envio, a tentativa falha e o motivo aparece no log de entregas.
O formato da entrega
Seção intitulada “O formato da entrega”Cada entrega é um POST com estes cabeçalhos, no padrão Standard Webhooks:
| Cabeçalho | Valor |
|---|---|
webhook-id | Id do evento, evt_ seguido de 32 caracteres hexadecimais. O mesmo id do corpo |
webhook-timestamp | Momento desta tentativa, em segundos Unix |
webhook-signature | A assinatura, v1, seguido do HMAC em base64. Veja Verificar a assinatura |
content-type | application/json |
user-agent | FatureiHoje-Webhooks/1.0 |
E este corpo:
{ "id": "evt_9b2f4c7e1a3d4f6b8c0e2a4d6f8b1c3e", "type": "client.updated", "api_version": "v1", "timestamp": "2026-09-16T14:30:00.000Z", "organization_id": "5c1e8f2a-3b4d-4c6e-8f0a-1b2c3d4e5f60", "data": { "object": { "object": "client", "id": "0d9a7c5e-3f1b-4d2a-8c6e-4a2b0c8e6f1d", "...": "..." }, "changed_fields": ["email", "phone"] }}| Campo | O que é |
|---|---|
id | Id do evento. O mesmo em todas as tentativas e no reenvio manual |
type | Nome do evento, do Catálogo de eventos |
api_version | Versão do contrato do objeto. Hoje, sempre v1 |
timestamp | Quando o evento aconteceu, em ISO 8601 UTC. Não muda entre tentativas |
organization_id | A empresa do evento |
data.object | O objeto, igual ao que o GET do recurso devolveria naquele instante |
data.changed_fields | Só nos eventos .updated: quais campos mudaram |
data.object_truncated | Só aparece quando o objeto veio resumido, e aí vale true |
Repare na diferença entre os dois horários: webhook-timestamp é o horário do envio, e muda a cada tentativa (é o que protege contra repetição maliciosa); timestamp, no corpo, é o horário do fato, e é por ele que você ordena.
O ping tem o mesmo formato, com type: "ping" e um objeto próprio:
{ "id": "evt_4e6a8c0b2d4f4a6c8e0b2d4f6a8c0e2b", "type": "ping", "api_version": "v1", "timestamp": "2026-09-16T14:30:00.000Z", "organization_id": "5c1e8f2a-3b4d-4c6e-8f0a-1b2c3d4e5f60", "data": { "object": { "object": "ping", "webhook_endpoint_id": "7f3a1c5e-9b2d-4f6a-8c0e-2b4d6f8a0c1e" } }}changed_fields
Seção intitulada “changed_fields”Nos eventos .updated, changed_fields lista os campos do objeto que mudaram naquela gravação, em ordem alfabética. updated_at nunca entra na lista, porque muda em toda gravação e não diz o que mudou.
Uma gravação pode gerar mais de um evento. Se a situação de uma OS mudou junto com a descrição, saem service_order.status_changed e service_order.updated, e o changed_fields do segundo traz os dois campos. As regras de cada área estão no Catálogo de eventos.
Gravação que não muda nada no objeto não gera .updated: salvar um cadastro sem alterar nenhum campo não manda evento.
O teto de 256 KB e o object_truncated
Seção intitulada “O teto de 256 KB e o object_truncated”O corpo da entrega tem teto de 256 KB, contados em bytes. Quando o objeto não cabe, data.object vem só com object e id, e data.object_truncated vem true:
{ "data": { "object": { "object": "sale", "id": "3c5e7a9b-1d3f-4b5d-8f1a-3c5e7a9b1d3f" }, "object_truncated": true }}Com object_truncated, busque o objeto pela API (GET /public/v1/sales/{id}, no exemplo). Ele chega como está agora, que pode ser diferente de como estava no instante do evento.
Eventos que chegam resumidos
Seção intitulada “Eventos que chegam resumidos”Alguns caminhos de gravação não montam o objeto completo. Deles, o evento sai sempre resumido, no mesmo formato do teto acima: data.object = { object, id } e data.object_truncated: true. São eles:
- o que o assistente do WhatsApp grava;
- o que as automações gravam;
- o que vem do Open Finance (importação e conciliação bancária);
- as rotinas automáticas: a que marca lançamentos e vendas como vencidos e a que gera os meses seguintes das recorrências sem fim;
- e, raramente, qualquer outra gravação em que o objeto não pôde ser montado na hora.
O tipo do evento é o certo (task.created, financial_transaction.updated…), mas o objeto não vem. Busque-o pela API. Num .updated resumido, changed_fields vem vazio, porque sem o objeto não dá para saber quais campos mudaram.
Trate object_truncated: true sempre do mesmo jeito, venha ele do teto de tamanho ou de um desses caminhos: busque o objeto pela API.
O que não gera evento
Seção intitulada “O que não gera evento”Algumas mudanças no objeto acontecem sem evento próprio. Elas são consequência de outra gravação, e o evento dessa outra gravação é o que você recebe.
- Vínculo apagado junto com o pai. Quando um registro é excluído, o que apontava para ele perde o vínculo (o campo vira
null) sem.updated. Por exemplo: as tarefas e os compromissos de um cliente, de uma OS, de um lançamento ou de um membro que foi excluído, e a OS de um compromisso excluído. Você recebe o.deleteddo pai. - Exclusão de produto. Os itens de venda perdem o
product_ide os movimentos de estoque do produto são apagados, semsale.updatednemstock.changed. Você recebe oproduct.deleted. - Renomear uma categoria não gera
product.updatednemservice.updatednos itens dela. - Número do recibo. O
receipt_numberde um recebimento da venda (payments[].receipt_number) sai denullpara um número (R-0001, por exemplo) quando alguém gera no painel, pela primeira vez, o recibo do lançamento desse recebimento. Isso acontece semsale.updated. - Nota fiscal.
invoice_lockedda venda ehas_active_invoicedos recebimentos mudam quando uma nota fiscal é emitida ou cancelada, semsale.updated. is_overdueda tarefa mudando sozinho, com a passagem do tempo, não geratask.updated.- Expiração de orçamento. O orçamento vencido só muda de situação quando alguém abre a lista de orçamentos. O
quote.status_changedda expiração sai nessa hora, e não no dia do vencimento.
Quando o dado precisa estar exato, releia o recurso pela API em vez de confiar só nos eventos. Veja Boas práticas.
Quem recebe o quê
Seção intitulada “Quem recebe o quê”Cada endpoint guarda quem o inscreveu: o membro, e a chave de API quando a inscrição veio pela API. Quem inscreveu é quem criou o endpoint ou, depois, quem mudou a URL ou os eventos, ou religou o endpoint.
Antes de cada envio, o Faturei Hoje confere se quem inscreveu ainda pode ler o módulo daquele evento: a chave continua ativa e com <modulo>:read, e o membro continua ativo e com acesso ao módulo, pelas mesmas regras do painel. Se não pode mais:
- a entrega daquele evento é fechada sem ser enviada, com situação
failede errosubscriber_access_lostno log; - isso não gasta tentativa e não conta como falha do endpoint, que continua ligado;
- os eventos dos outros módulos, que quem inscreveu ainda lê, continuam chegando;
- dono e administradores recebem um e-mail avisando, e o aviso só volta a sair depois que uma entrega der certo e o acesso for perdido de novo.
Para voltar a receber, devolva o acesso a quem inscreveu, ou edite o endpoint com um membro (ou chave) que tenha o acesso: quem edita a URL ou os eventos passa a ser quem inscreveu.
Os eventos de venda (sale.*) nunca levam account_id nem finance_transactions, mesmo que quem inscreveu leia o financeiro: se inscrever em vendas exige ler só vendas. Os lançamentos têm os eventos deles, financial_transaction.*, que exigem ler o financeiro.
A empresa que muda para um plano sem a API para de receber entregas, inclusive o ping: elas são fechadas sem envio, com o erro plan_without_api.
Rotação e revogação da chave de API
Seção intitulada “Rotação e revogação da chave de API”- Rotação com convivência (carência maior que zero, como as opções de 1 hora e de 24 horas do painel): os endpoints inscritos pela chave antiga passam para a chave nova na mesma ação, e nada para de chegar.
- Rotação “Parar agora”, o gesto de quem suspeita que a chave vazou: os endpoints inscritos pela chave antiga são pausados, porque quem pegou a chave pode ter cadastrado um endpoint para receber os seus dados. Eles aparecem com
status: "disabled"edisabled_reason: "emergency_key_rotation", e dono e administradores recebem um e-mail com a lista (só o domínio de cada URL). Revise a lista e religue o que for seu. - Chave achada pelo GitHub em repositório público, quando a inscrição no programa de varredura de segredo do GitHub estiver ativa: a chave é revogada automaticamente e os endpoints inscritos por ela são pausados do mesmo jeito, com o mesmo
disabled_reason: "emergency_key_rotation", e dono e administradores recebem um e-mail. A inscrição ainda não está ativa. - Chave revogada ou vencida (fora o caso acima): os endpoints inscritos por ela deixam de receber, pela regra de acesso acima.
Veja Rotação.
Limites
Seção intitulada “Limites”Quantos endpoints a empresa pode ter cadastrados, ligados ou não:
| Plano | Endpoints de webhook |
|---|---|
| Grátis | 0 |
| Start | 3 |
| Pleno | 10 |
| Supra | 25 |
No envio:
- até 5 entregas simultâneas por endpoint;
- 1 entrega por vez para o endpoint que está falhando, até uma entrega para ele dar certo;
- até 10 entregas simultâneas por empresa, somando todos os endpoints. Um endpoint lento não segura a fila das outras empresas;
- o endpoint que só falha por 3 dias seguidos é desligado, e dono e administradores recebem um e-mail. Veja Desligamento automático.
Os dados enviados passam a ser responsabilidade de quem configurou
Seção intitulada “Os dados enviados passam a ser responsabilidade de quem configurou”Cada entrega leva dados da empresa para fora do Faturei Hoje, para o sistema que o endpoint aponta. A partir do momento em que o dado chega a esse sistema, ele passa a ser responsabilidade da empresa que configurou a integração, inclusive perante a LGPD: quem guarda, por quanto tempo, quem acessa e como descarta.
Do nosso lado, os eventos e o log de entregas ficam guardados por 30 dias e depois são apagados. O envio roda na nossa própria infraestrutura, sem nenhum serviço de terceiro no meio.
Próximo passo
Seção intitulada “Próximo passo”- Verificar a assinatura: o código para Node.js, PHP e Python.
- Catálogo de eventos: cada evento, quando sai e o exemplo do corpo.
- Entregas e novas tentativas: o que conta como sucesso, a agenda e o reenvio.
- Boas práticas: responder rápido, ignorar duplicata e ordenar.