Pular para o conteúdo principal

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

  1. Vá para Configuração > Webhooks na sua conta Frihet
  2. Clique em Criar webhook
  3. Digite um nome descritivo, a URL de destino e selecione os eventos que deseja receber
  4. Opcionalmente, defina um segredo para a verificação HMAC (recomendado)
  5. 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
CampoTipoObrigatórioDescrição
urlstringSimURL de destino (HTTPS obrigatório em produção)
eventsstring[]SimLista de tipos de evento a receber
secretstringNãoSegredo para assinatura HMAC-SHA256 (recomendado)
activebooleanNãoEstado 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 localhost e 127.0.0.1 durante 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)

EventoDescrição
invoice.createdUma nova fatura foi criada
invoice.updatedUma fatura existente foi modificada
invoice.paidUma fatura foi marcada como paga
invoice.overdueUma fatura ultrapassou sua data de vencimento

Despesas (2 eventos)

EventoDescrição
expense.createdUma nova despesa foi registrada
expense.updatedUma despesa existente foi modificada

Orçamentos (4 eventos)

EventoDescrição
quote.createdUm novo orçamento foi criado
quote.updatedUm orçamento existente foi modificado
quote.acceptedUm cliente aceitou um orçamento (mudança de status para accepted)
quote.rejectedUm cliente rejeitou um orçamento (mudança de status para rejected)

Clientes (2 eventos)

EventoDescrição
client.createdUm novo cliente foi cadastrado
client.updatedOs dados de um cliente foram modificados

Produtos (2 eventos)

EventoDescrição
product.createdUm novo produto ou serviço foi criado
product.updatedUm 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çalhoDescriçãoExemplo
Content-TypeTipo de conteúdoapplication/json
X-Frihet-EventTipo de eventoinvoice.paid
X-Frihet-Delivery-IdIdentificador único da entregad4e5f6a7b8c9
X-Frihet-TimestampCarimbo de data/hora ISO 86012026-02-12T14:30:00.000Z
X-Frihet-SignatureAssinatura HMAC-SHA256 do payloadsha256=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:

  1. Pegar o corpo da requisição bruto (raw body)
  2. Calcular o HMAC-SHA256 usando o segredo como chave
  3. Comparar o resultado com o valor do cabeçalho X-Frihet-Signature (sem o prefixo sha256=)

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:

TentativaAtrasoTempo acumulado
1 (inicial)imediato0s
2 (primeira nova tentativa)2 segundos2s
3 (segunda nova tentativa)4 segundos6s
  • 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:

  1. Vá para Ajustes > Desenvolvedores > Webhooks
  2. Clique no webhook que deseja inspecionar
  3. 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, retrying ou failed

Problemas comuns

SintomaCausa provávelSolução
Todas as entregas falham com timeoutEndpoint demora mais de 30s para responderAceite o evento com um 200 imediato e processe de forma assíncrona
Erro SSL/TLSCertificado expirado ou cadeia incompletaRenove o certificado e verifique a cadeia com openssl s_client
Código 403 sistemáticoFirewall bloqueando requisições POST de entradaPermita tráfego dos IPs do Google Cloud (us-central1)
Código 502/503Servidor caído ou em manutençãoVerifique 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

  1. Verifique se a URL do webhook está correta e acessível pela internet
  2. Verifique se o webhook está em status ativo no painel
  3. Verifique os logs de entrega para ver se há erros
  4. 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

  1. Verifique se você está usando o raw body da requisição (antes de fazer o parse do JSON)
  2. Confirme se o segredo no seu código corresponde ao configurado no painel da Frihet
  3. Não modifique nem reformate o body antes de verificar a assinatura
  4. 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

  1. 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
  2. Verifique o histórico de entregas para confirmar o status de cada tentativa
  3. 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.