Webhooks
Os webhooks da Frihet permitem receber notificações HTTP em tempo real quando eventos ocorrem na sua conta. Em vez de fazer polling na API, configure uma URL e a Frihet enviará um POST com os dados do evento no momento em que ele ocorrer.
Configuração
Você pode gerenciar os webhooks a partir do painel da Frihet (Configuração > Webhooks) ou programaticamente através da API REST.
Pelo painel
- Vá para Configuração > Webhooks na sua conta Frihet
- Clique em Criar webhook
- Digite um nome descritivo, a URL de destino e selecione os eventos que deseja receber
- Opcionalmente, defina um segredo para a verificação HMAC (recomendado)
- Salve o webhook
API REST de webhooks
Gerencie webhooks programaticamente com os endpoints CRUD completos.
Listar webhooks
GET /v1/webhooks
curl https://api.frihet.io/v1/webhooks \
-H "X-API-Key: fri_tu-clave-aqui"
Resposta (200):
{
"data": [
{
"id": "wh_abc123",
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "invoice.created"],
"active": true,
"createdAt": "2026-02-01T10:00:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
Obter webhook
GET /v1/webhooks/:id
curl https://api.frihet.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: fri_tu-clave-aqui"
Criar webhook
POST /v1/webhooks
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
url | string | Sim | URL de destino (HTTPS obrigatório em produção) |
events | string[] | Sim | Lista de tipos de evento a receber |
secret | string | Não | Segredo para assinatura HMAC-SHA256 (recomendado) |
active | boolean | Não | Estado do webhook (padrão: true) |
curl -X POST https://api.frihet.io/v1/webhooks \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "expense.created"],
"secret": "mi-secreto-hmac"
}'
Resposta (201):
{
"id": "wh_def456",
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "expense.created"],
"active": true,
"createdAt": "2026-03-18T10:00:00.000Z"
}
Atualizar webhook
PATCH /v1/webhooks/:id
Você só precisa enviar os campos que deseja modificar.
curl -X PATCH https://api.frihet.io/v1/webhooks/wh_def456 \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"events": ["invoice.paid", "invoice.created", "expense.created"],
"active": false
}'
Resposta (200): Objeto de webhook atualizado.
Excluir webhook
DELETE /v1/webhooks/:id
curl -X DELETE https://api.frihet.io/v1/webhooks/wh_def456 \
-H "X-API-Key: fri_tu-clave-aqui"
Resposta: 204 No Content
Requisitos da URL:
- Deve usar HTTPS (HTTP é permitido apenas para
localhoste127.0.0.1durante o desenvolvimento) - IPs privadas não são permitidas (10.x, 172.16-31.x, 192.168.x) para evitar ataques SSRF
- Deve responder com um código 2xx em menos de 30 segundos
Limites:
- Máximo de 20 webhooks por conta
- Payload máximo de 100 KB por entrega
Tipos de evento
A Frihet emite 14 tipos de evento, agrupados por recurso.
Faturas (4 eventos)
| Evento | Descrição |
|---|---|
invoice.created | Uma nova fatura foi criada |
invoice.updated | Uma fatura existente foi modificada |
invoice.paid | Uma fatura foi marcada como paga |
invoice.overdue | Uma fatura ultrapassou sua data de vencimento |
Despesas (2 eventos)
| Evento | Descrição |
|---|---|
expense.created | Uma nova despesa foi registrada |
expense.updated | Uma despesa existente foi modificada |
Orçamentos (4 eventos)
| Evento | Descrição |
|---|---|
quote.created | Um novo orçamento foi criado |
quote.updated | Um orçamento existente foi modificado |
quote.accepted | Um cliente aceitou um orçamento (mudança de status para accepted) |
quote.rejected | Um cliente rejeitou um orçamento (mudança de status para rejected) |
Clientes (2 eventos)
| Evento | Descrição |
|---|---|
client.created | Um novo cliente foi cadastrado |
client.updated | Os dados de um cliente foram modificados |
Produtos (2 eventos)
| Evento | Descrição |
|---|---|
product.created | Um novo produto ou serviço foi criado |
product.updated | Um produto ou serviço existente foi modificado |
Estrutura do payload
Cada entrega de webhook é uma requisição POST com corpo JSON. A estrutura é a mesma para todos os tipos de evento:
{
"event": "invoice.paid",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"id": "inv_abc123",
"clientName": "Acme S.L.",
"items": [
{ "description": "Consultoria", "quantity": 10, "unitPrice": 75 }
],
"total": 750,
"status": "paid",
"paidAt": "2026-02-12T14:29:58.000Z",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-02-12T14:30:00.000Z"
}
}
O campo data contém o estado completo do recurso no momento do evento.
Cabeçalhos de entrega
Cada requisição de webhook inclui os seguintes cabeçalhos:
| Cabeçalho | Descrição | Exemplo |
|---|---|---|
Content-Type | Tipo de conteúdo | application/json |
X-Frihet-Event | Tipo de evento | invoice.paid |
X-Frihet-Delivery-Id | Identificador único da entrega | d4e5f6a7b8c9 |
X-Frihet-Timestamp | Carimbo de data/hora ISO 8601 | 2026-02-12T14:30:00.000Z |
X-Frihet-Signature | Assinatura HMAC-SHA256 do payload | sha256=a1b2c3d4e5f6... |
O cabeçalho X-Frihet-Signature só é incluído se você configurou um segredo no webhook.
Verificação de assinatura
Se você configurar um segredo ao criar o webhook, a Frihet assina cada payload com HMAC-SHA256. O processo de verificação consiste em:
- Pegar o corpo da requisição bruto (raw body)
- Calcular o HMAC-SHA256 usando o segredo como chave
- Comparar o resultado com o valor do cabeçalho
X-Frihet-Signature(sem o prefixosha256=)
Com o SDK (recomendado)
import { Webhooks } from '@frihet/sdk';
app.post('/webhook/frihet', express.raw({ type: 'application/json' }), (req, res) => {
const isValid = Webhooks.verifySignature(
req.body,
req.headers['x-frihet-signature'],
process.env.FRIHET_WEBHOOK_SECRET,
);
if (!isValid) return res.status(401).send('Assinatura inválida');
const event = JSON.parse(req.body.toString());
console.log(req.headers['x-frihet-event'], event.data);
res.sendStatus(200);
});
Exemplo manual em Node.js
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const receivedSignature = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}
// Uso em um servidor Express
app.post('/webhook/frihet', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-frihet-signature'];
const secret = process.env.FRIHET_WEBHOOK_SECRET;
if (!signature || !verifyWebhookSignature(req.body.toString(), signature, secret)) {
return res.status(401).send('Assinatura inválida');
}
const event = JSON.parse(req.body.toString());
const eventType = req.headers['x-frihet-event'];
console.log(`Evento recebido: ${eventType}`, event.data);
// Processar o evento de acordo com seu tipo
switch (eventType) {
case 'invoice.paid':
// Atualizar seu sistema de contabilidade
break;
case 'expense.created':
// Notificar a equipe financeira
break;
}
res.status(200).send('OK');
});
Exemplo em Python
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = 'tu-secreto-aqui'
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
received = signature.replace('sha256=', '')
return hmac.compare_digest(expected, received)
@app.route('/webhook/frihet', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Frihet-Signature', '')
if not verify_signature(request.data, signature, WEBHOOK_SECRET):
abort(401)
event = request.get_json()
event_type = request.headers.get('X-Frihet-Event')
print(f'Evento recebido: {event_type}')
if event_type == 'invoice.paid':
# Atualizar seu sistema de contabilidade
pass
elif event_type == 'quote.accepted':
# Converter orçamento em fatura
pass
return 'OK', 200
Importante: Sempre use uma comparação em tempo constante (timingSafeEqual no Node.js, compare_digest no Python) para evitar ataques de timing.
Política de novas tentativas
Se a entrega de um webhook falhar (código de resposta não-2xx ou timeout), a Frihet tenta novamente automaticamente com backoff exponencial:
| Tentativa | Atraso | Tempo acumulado |
|---|---|---|
| 1 (inicial) | imediato | 0s |
| 2 (primeira nova tentativa) | 2 segundos | 2s |
| 3 (segunda nova tentativa) | 4 segundos | 6s |
- Máximo de 3 tentativas por entrega (1 inicial + 2 novas tentativas)
- Atraso máximo de 30 segundos entre as novas tentativas
- Timeout de 30 segundos por requisição
- Se as 3 tentativas falharem, a entrega é marcada como
failed
As novas tentativas são processadas por meio de um job que é executado a cada 5 minutos, garantindo a confiabilidade mesmo que o processo principal seja reiniciado.
Você pode consultar o histórico de entregas de cada webhook a partir do painel da Frihet, incluindo o código de resposta, o corpo da resposta (primeiros 1000 caracteres) e os erros de cada tentativa.
Testes
Pelo painel da Frihet você pode enviar um evento de teste para qualquer webhook configurado. O payload de teste tem a seguinte estrutura:
{
"event": "webhook.test",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"message": "This is a test webhook from Frihet ERP"
}
}
Isso permite verificar se seu endpoint está acessível, se a assinatura é validada corretamente e se seu sistema processa os eventos sem erros.
Boas práticas
Responda rapidamente
Seu endpoint deve responder com um código 200 o mais rápido possível. Se você precisar realizar um processamento pesado (enviar e-mails, atualizar bancos de dados externos, etc.), aceite o evento e processe-o de forma assíncrona em uma fila de trabalho.
Gerencie a idempotência
É possível que um mesmo evento seja entregue mais de uma vez (por exemplo, se seu servidor respondeu com um timeout mas processou o evento). Use o campo X-Frihet-Delivery-Id como chave de idempotência para evitar duplicatas.
Sempre verifique a assinatura
Nunca confie em um webhook sem verificar o cabeçalho X-Frihet-Signature. Qualquer ator com acesso à sua URL poderia enviar payloads falsos.
Use HTTPS
Em produção, seu endpoint deve ser protegido com HTTPS. A Frihet rejeita URLs HTTP (exceto em desenvolvimento local).
Monitore falhas
Verifique periodicamente os logs de entrega no painel da Frihet. Se você vir entregas falhas de forma recorrente, verifique se seu endpoint está disponível e responde em menos de 30 segundos.
Filtre os eventos
Assine apenas os eventos que você precisa. Cada webhook pode ouvir um ou vários tipos de evento. Quanto menos eventos desnecessários você processar, menor será a carga em seu servidor.
Depuração de entregas
A Frihet registra o resultado de cada entrega de webhook. Você pode consultá-lo pelo painel:
- Vá para Ajustes > Desenvolvedores > Webhooks
- Clique no webhook que deseja inspecionar
- Abra a aba Entregas
Cada entrada mostra:
- Código de resposta HTTP retornado pelo seu endpoint
- Tempo de resposta em milissegundos
- Corpo da resposta (primeiros 1000 caracteres)
- Data e hora de cada tentativa (incluindo as novas tentativas)
- Status final:
delivered,retryingoufailed
Problemas comuns
| Sintoma | Causa provável | Solução |
|---|---|---|
| Todas as entregas falham com timeout | Endpoint demora mais de 30s para responder | Aceite o evento com um 200 imediato e processe de forma assíncrona |
| Erro SSL/TLS | Certificado expirado ou cadeia incompleta | Renove o certificado e verifique a cadeia com openssl s_client |
| Código 403 sistemático | Firewall bloqueando requisições POST de entrada | Permita tráfego dos IPs do Google Cloud (us-central1) |
| Código 502/503 | Servidor caído ou em manutenção | Verifique os logs do seu servidor e confira se o processo está ativo |
Botão de teste
Na tela de detalhes de cada webhook, o botão Enviar teste envia um evento sintético webhook.test para sua URL. Use este botão para verificar:
- Se a URL está acessível pela internet
- Se a assinatura HMAC é validada corretamente
- Seu servidor responde com um código
2xx
O resultado do teste aparece imediatamente no log de entregas.
Resolução de problemas
Não recebo webhooks
- Verifique se a URL do webhook está correta e acessível pela internet
- Verifique se o webhook está em status ativo no painel
- Verifique os logs de entrega para ver se há erros
- Se você usa um firewall, certifique-se de que as requisições POST de entrada do Google Cloud (
us-central1) estão permitidas
As assinaturas não coincidem
- Verifique se você está usando o raw body da requisição (antes de fazer o parse do JSON)
- Confirme se o segredo no seu código corresponde ao configurado no painel da Frihet
- Não modifique nem reformate o body antes de verificar a assinatura
- Verifique se seu framework não está fazendo o parse automático do body antes que você possa acessar o raw
As novas tentativas não chegam
- As novas tentativas são processadas a cada 5 minutos. Se seu endpoint ficou fora do ar brevemente, pode ser que as tentativas já tenham se esgotado
- Verifique o histórico de entregas para confirmar o status de cada tentativa
- Se as 3 tentativas falharem, a entrega não será tentada novamente automaticamente
Payload muito grande
Se o recurso associado ao evento for muito grande (muitas linhas em uma fatura, campos extensos), o payload pode exceder o limite de 100 KB e a entrega será rejeitada. Simplifique os dados do recurso ou entre em contato com o suporte se precisar de um limite superior.