Integrando com o Efrat
A API do Efrat permite que outros sistemas (ERP, CRM, app, automações) consultem e gravem dados da sua empresa no Efrat, e recebam avisos automáticos quando algo muda.
Como funciona, em resumo
- Crie um Cliente de integração dentro do Efrat, marcando o que o sistema externo pode fazer.
- Guarde o Client ID e o Client Secret gerados — eles são a "senha" do sistema externo.
- Faça o login com essas chaves e receba um token de acesso (válido por 60 minutos).
- Chame as rotas enviando o token no cabeçalho
Authorization. - Opcional: cadastre Webhooks para receber os dados no seu sistema sempre que um registro for incluído, alterado ou excluído.
Informações gerais
| URL base | https://api.efrat.com.br |
|---|---|
| Protocolo | HTTPS obrigatório |
| Formato | JSON (UTF-8) no envio e na resposta — Content-Type: application/json |
| Autenticação | Token Bearer (JWT) obtido com Client ID + Client Secret |
| Nomes dos campos | camelCase (ex.: dueDate, totalRecords) |
| Identificadores | UUID (ex.: 3fa85f64-5717-4562-b3fc-2c963f66afa6) |
| Rotas disponíveis | — rotas, nos módulos Sistema, Único, Igreja, Financeiro, Agenda, Fiscal e Projetos |
Seus dados ficam isolados. Cada cliente de integração pertence a uma empresa e só enxerga e grava dados dessa empresa. Não é possível acessar dados de outra empresa, mesmo conhecendo o identificador de um registro.
1. Criar um cliente de integração
O cliente de integração é a credencial de um sistema externo. Crie um para cada sistema que vai se conectar — por exemplo, um para o seu ERP e outro para o seu CRM. Assim cada um tem suas próprias permissões e pode ser desativado sem afetar os outros.
- Entre no Efrat com um usuário que tenha permissão em Clientes de Integração (normalmente o administrador da empresa).
- Abra o módulo Sistema → menu Configurações → Clientes de Integração.
- Clique em Novo Client.
- Em Nome, identifique o sistema (ex.: ERP Financeiro, CRM Vendas).
- Em Escopos, marque, para cada tela, o que o sistema pode fazer: Consultar, Incluir, Alterar e/ou Excluir. Só aparecem as telas liberadas no plano contratado.
- Deixe Ativo marcado e clique em Salvar.
Libere somente o necessário. Se o sistema externo só precisa ler o cadastro de pessoas, marque apenas Único › Pessoa — Consultar. Escopos podem ser alterados depois, a qualquer momento, na mesma tela.
Gerenciando clientes existentes
- Editar: muda o nome, os escopos ou desativa o cliente. Mudanças de escopo valem a partir do próximo login (token novo).
- Desativar: bloqueia novos logins na hora. Tokens já emitidos continuam válidos até expirarem (no máximo 60 minutos).
- Gerar novo secret: cria um secret novo e invalida o anterior para novos logins — use se o secret vazar.
- Último uso: a lista mostra quando cada cliente fez login pela última vez.
2. Client ID e Client Secret
Ao salvar um cliente novo, o Efrat mostra uma janela com as duas chaves:
| Chave | Formato | É segredo? | Onde aparece depois |
|---|---|---|---|
| Client ID | ic_ seguido de 24 caracteres (ex.: ic_4f9a1c0e7b2d8a6e3c5b1f07) | Não — identifica o cliente | Sempre visível na lista de Clientes de Integração |
| Client Secret | Texto longo e aleatório | Sim — funciona como senha | Nunca mais. Só é exibido na criação e ao gerar um novo secret |
Copie o Client Secret na hora e guarde em local seguro (cofre de senhas, variável de ambiente do servidor). O Efrat não guarda o secret em texto — se ele se perder, use Gerar novo secret.
Nunca coloque o secret em código de navegador, aplicativo mobile ou repositório público: ele deve ficar só no servidor do sistema que se integra.
3. Login: obter o token de acesso
Troque o Client ID e o Client Secret por um token. O envio é sempre no corpo da requisição, em JSON (não use cabeçalho nem parâmetro de URL).
POST /api/integration/token
Content-Type: application/json
{
"clientId": "ic_4f9a1c0e7b2d8a6e3c5b1f07",
"clientSecret": "SEU_CLIENT_SECRET"
}
Resposta de sucesso — 200 OK
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": 3600
}
| Campo | Descrição |
|---|---|
accessToken | Token a ser enviado em todas as requisições. |
tokenType | Sempre Bearer. |
expiresIn | Validade em segundos (3600 = 60 minutos). Depois disso, faça o login de novo. |
Erros do login
| Código | Motivo |
|---|---|
401 | Client ID ou Client Secret inválidos, ou cliente desativado. Resposta: {"message": "Client id ou client secret inválidos."} |
400 | Corpo ausente ou JSON inválido. |
Exemplos
curl -X POST "https://api.efrat.com.br/api/integration/token" \
-H "Content-Type: application/json" \
-d '{"clientId":"ic_4f9a1c0e7b2d8a6e3c5b1f07","clientSecret":"SEU_CLIENT_SECRET"}'// Node.js 18+ (fetch nativo)
const res = await fetch('https://api.efrat.com.br/api/integration/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: process.env.EFRAT_CLIENT_ID,
clientSecret: process.env.EFRAT_CLIENT_SECRET,
}),
});
if (!res.ok) throw new Error(`Login falhou: ${res.status}`);
const { accessToken, expiresIn } = await res.json();import os, requests
res = requests.post(
"https://api.efrat.com.br/api/integration/token",
json={"clientId": os.environ["EFRAT_CLIENT_ID"], "clientSecret": os.environ["EFRAT_CLIENT_SECRET"]},
timeout=30,
)
res.raise_for_status()
token = res.json()["accessToken"]using System.Net.Http.Json;
var http = new HttpClient { BaseAddress = new Uri("https://api.efrat.com.br") };
var res = await http.PostAsJsonAsync("/api/integration/token", new
{
clientId = Environment.GetEnvironmentVariable("EFRAT_CLIENT_ID"),
clientSecret = Environment.GetEnvironmentVariable("EFRAT_CLIENT_SECRET"),
});
res.EnsureSuccessStatusCode();
var login = await res.Content.ReadFromJsonAsync<TokenResponse>();
record TokenResponse(string AccessToken, string TokenType, int ExpiresIn);<?php
$ch = curl_init('https://api.efrat.com.br/api/integration/token');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'clientId' => getenv('EFRAT_CLIENT_ID'),
'clientSecret' => getenv('EFRAT_CLIENT_SECRET'),
]),
]);
$body = json_decode(curl_exec($ch), true);
$token = $body['accessToken'];Reaproveite o token. Faça o login uma vez e use o mesmo token por até 60 minutos. Renove alguns minutos
antes de vencer, ou quando receber 401. Não existe token de renovação (refresh token): basta fazer o login de novo.
4. Fazendo requisições
Em toda requisição, envie o token no cabeçalho Authorization:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Nas inclusões e alterações (POST/PUT), envie também Content-Type: application/json e o registro em JSON no corpo.
Exemplo: consultar o cadastro de pessoas (Único)
Escopo necessário: PESSOA:view (Único › Pessoa — Consultar).
GET /api/uniques?page=1&pageSize=20&name=Silva
Authorization: Bearer SEU_TOKEN
{
"data": [
{
"id": "8229f2c4-da94-4c25-8c51-ceef19fef98a",
"name": "Maria da Silva",
"cpfCnpj": "12345678900",
"typePerson": "Fisica",
"fantasy": null,
"primaryCellphone": "47999999999",
"status": true
}
],
"totalRecords": 134,
"page": 1,
"pageSize": 20
}
Exemplo: incluir um centro de custo
Escopo necessário: CENTRO_CUSTO:create (Financeiro › Centro de Custo — Incluir).
POST /api/cost-centers
Authorization: Bearer SEU_TOKEN
Content-Type: application/json
{ "code": "0010", "description": "Marketing" }
Resposta 201 Created com o registro criado:
{ "id": "922fabe3-772b-49aa-a3d4-8f149cd5e2b5", "code": "0010", "description": "Marketing", "status": true }
# Consultar
curl "https://api.efrat.com.br/api/uniques?page=1&pageSize=20" \
-H "Authorization: Bearer $TOKEN"
# Incluir
curl -X POST "https://api.efrat.com.br/api/cost-centers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"code":"0010","description":"Marketing"}'const api = (path, options = {}) =>
fetch(`https://api.efrat.com.br${path}`, {
...options,
headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', ...options.headers },
});
// Consultar
const pessoas = await (await api('/api/uniques?page=1&pageSize=20')).json();
// Incluir
const res = await api('/api/cost-centers', {
method: 'POST',
body: JSON.stringify({ code: '0010', description: 'Marketing' }),
});
const criado = await res.json();s = requests.Session()
s.headers.update({"Authorization": f"Bearer {token}"})
base = "https://api.efrat.com.br"
# Consultar
pessoas = s.get(f"{base}/api/uniques", params={"page": 1, "pageSize": 20}, timeout=30).json()
# Incluir
criado = s.post(f"{base}/api/cost-centers", json={"code": "0010", "description": "Marketing"}, timeout=30).json()http.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", login!.AccessToken);
// Consultar
var pessoas = await http.GetFromJsonAsync<JsonElement>("/api/uniques?page=1&pageSize=20");
// Incluir
var res = await http.PostAsJsonAsync("/api/cost-centers", new { code = "0010", description = "Marketing" });
res.EnsureSuccessStatusCode();<?php
function efrat($method, $path, $token, $body = null) {
$ch = curl_init('https://api.efrat.com.br' . $path);
$headers = ['Authorization: Bearer ' . $token, 'Content-Type: application/json'];
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return [$status, $response];
}
[$status, $pessoas] = efrat('GET', '/api/uniques?page=1&pageSize=20', $token);
[$status, $criado] = efrat('POST', '/api/cost-centers', $token, ['code' => '0010', 'description' => 'Marketing']);Verbos HTTP
| Verbo | Uso | Escopo (ação) | Resposta de sucesso |
|---|---|---|---|
| GET | Consultar (listar ou buscar um registro) | Consultar (view) | 200 OK com os dados |
| POST | Incluir um registro ou executar uma ação (ex.: cancelar, emitir) | Incluir (create) ou Alterar (update), conforme a rota | 201 Created ou 200 OK |
| PUT | Alterar um registro (envie o registro completo) | Alterar (update) | 200 OK com o registro atualizado |
| DELETE | Excluir | Excluir (delete) | 204 No Content |
O escopo exato de cada rota está indicado em Todas as rotas.
Escopos e permissões
Cada rota pertence a uma tela do Efrat e exige uma ação. O escopo é escrito como
TELA:ação — por exemplo, LANCAMENTO:view permite consultar lançamentos financeiros e
LANCAMENTO:create permite incluí-los.
| Ação no cadastro | Código | Permite |
|---|---|---|
| Consultar | view | Listar, buscar e baixar dados da tela |
| Incluir | create | Criar registros |
| Alterar | update | Editar registros e executar ações sobre eles |
| Excluir | delete | Excluir registros |
Uma rota só responde se as três condições forem verdadeiras:
- O escopo da rota está marcado no cliente de integração;
- A tela faz parte do plano contratado pela empresa;
- A assinatura da empresa está ativa.
Caso contrário, a resposta é 403 Forbidden.
Rotas que não estão disponíveis para integração: usuários e permissões, login de usuários, parâmetros do sistema, cadastro de webhooks e de clientes de integração, assinatura, configurações de gateways de pagamento, WhatsApp, Google, Jira, Trello e configuração fiscal, campanhas de WhatsApp e dados de crianças. Essas operações só podem ser feitas por usuários dentro do Efrat.
Telas disponíveis
Paginação, filtros e datas
Listas paginadas
As listas aceitam page (começa em 1) e pageSize na query string e respondem neste formato:
{
"data": [ ... ],
"totalRecords": 134,
"page": 1,
"pageSize": 20
}
Para ler tudo, aumente page até ter lido totalRecords registros. O tamanho máximo de página varia
por rota (normalmente 100) — valores maiores são ajustados automaticamente.
// Lendo todas as páginas
let page = 1, all = [];
while (true) {
const r = await (await api(`/api/uniques?page=${page}&pageSize=100`)).json();
all.push(...r.data);
if (all.length >= r.totalRecords || r.data.length === 0) break;
page++;
}
Filtros
Os filtros de cada lista são parâmetros de query string (ex.: ?status=true&search=maria) e estão
descritos em cada rota na referência. Filtros não informados são ignorados.
Datas e valores
- Datas no formato ISO 8601:
2026-10-31ou2026-10-31T14:30:00Z. - Na Agenda, data/hora dos compromissos são no horário local da empresa, sem fuso (ex.:
2026-10-31T14:30:00). - Valores monetários são números decimais com ponto:
1250.90. - Campos de tipo (ex.:
typePerson,type) usam texto:"Fisica","Receber"— os valores aceitos aparecem em cada rota.
Erros
| Código | Significado | O que fazer |
|---|---|---|
400 | Dados inválidos (campo obrigatório faltando, valor fora da regra) | Leia a mensagem e corrija o envio |
401 | Token ausente, inválido ou vencido | Faça o login de novo e repita a requisição |
403 | Sem escopo para a rota, tela fora do plano, assinatura bloqueada ou rota indisponível para integração | Revise os escopos do cliente de integração |
404 | Registro não encontrado nesta empresa | Confira o identificador |
500 | Erro interno | Tente novamente depois; se persistir, contate o suporte |
Formato das mensagens
Erros de regra de negócio:
{ "message": "Já existe um centro de custo com este código." }
Erros de formato do JSON enviado (campos com tipo errado ou obrigatórios ausentes):
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": { "Description": ["The Description field is required."] }
}
Exemplos completos
Cliente pronto para uso: faz o login, guarda o token, renova quando vence e repete a requisição uma vez em caso de 401.
// efrat.js — Node.js 18+
const BASE = 'https://api.efrat.com.br';
let token = null, expiresAt = 0;
async function login() {
const res = await fetch(`${BASE}/api/integration/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ clientId: process.env.EFRAT_CLIENT_ID, clientSecret: process.env.EFRAT_CLIENT_SECRET }),
});
if (!res.ok) throw new Error(`Login Efrat falhou (${res.status})`);
const data = await res.json();
token = data.accessToken;
expiresAt = Date.now() + (data.expiresIn - 120) * 1000; // renova 2 min antes
}
export async function efrat(method, path, body, retried = false) {
if (!token || Date.now() > expiresAt) await login();
const res = await fetch(`${BASE}${path}`, {
method,
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
if (res.status === 401 && !retried) { token = null; return efrat(method, path, body, true); }
if (!res.ok) throw new Error(`${method} ${path} → ${res.status}: ${await res.text()}`);
return res.status === 204 ? null : res.json();
}
// Uso
const lancamentos = await efrat('GET', '/api/financial-entries?page=1&pageSize=50&type=Receber');# efrat.py
import os, time, requests
BASE = "https://api.efrat.com.br"
class Efrat:
def __init__(self):
self.token, self.expires_at = None, 0
self.http = requests.Session()
def _login(self):
r = self.http.post(f"{BASE}/api/integration/token", json={
"clientId": os.environ["EFRAT_CLIENT_ID"],
"clientSecret": os.environ["EFRAT_CLIENT_SECRET"],
}, timeout=30)
r.raise_for_status()
data = r.json()
self.token = data["accessToken"]
self.expires_at = time.time() + data["expiresIn"] - 120
def request(self, method, path, body=None, retried=False):
if not self.token or time.time() > self.expires_at:
self._login()
r = self.http.request(method, f"{BASE}{path}", json=body, timeout=60,
headers={"Authorization": f"Bearer {self.token}"})
if r.status_code == 401 and not retried:
self.token = None
return self.request(method, path, body, True)
r.raise_for_status()
return None if r.status_code == 204 else r.json()
efrat = Efrat()
lancamentos = efrat.request("GET", "/api/financial-entries?page=1&pageSize=50&type=Receber")// EfratClient.cs — .NET 8+
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
public sealed class EfratClient(HttpClient http, string clientId, string clientSecret)
{
private string? _token;
private DateTime _expiresAt;
private async Task LoginAsync()
{
var res = await http.PostAsJsonAsync("https://api.efrat.com.br/api/integration/token", new { clientId, clientSecret });
res.EnsureSuccessStatusCode();
var data = (await res.Content.ReadFromJsonAsync<TokenResponse>())!;
_token = data.AccessToken;
_expiresAt = DateTime.UtcNow.AddSeconds(data.ExpiresIn - 120);
}
public async Task<HttpResponseMessage> SendAsync(HttpMethod method, string path, object? body = null, bool retried = false)
{
if (_token is null || DateTime.UtcNow > _expiresAt) await LoginAsync();
var req = new HttpRequestMessage(method, "https://api.efrat.com.br" + path);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
if (body is not null) req.Content = JsonContent.Create(body);
var res = await http.SendAsync(req);
if (res.StatusCode == HttpStatusCode.Unauthorized && !retried) { _token = null; return await SendAsync(method, path, body, true); }
return res;
}
private sealed record TokenResponse(string AccessToken, string TokenType, int ExpiresIn);
}<?php
// efrat.php
class Efrat {
private $base = 'https://api.efrat.com.br';
private $token = null;
private $expiresAt = 0;
private function call($method, $url, $headers, $body = null) {
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 60]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$resp = curl_exec($ch);
return [curl_getinfo($ch, CURLINFO_HTTP_CODE), json_decode($resp, true)];
}
private function login() {
[$status, $data] = $this->call('POST', $this->base . '/api/integration/token', ['Content-Type: application/json'],
['clientId' => getenv('EFRAT_CLIENT_ID'), 'clientSecret' => getenv('EFRAT_CLIENT_SECRET')]);
if ($status !== 200) throw new Exception('Login Efrat falhou: ' . $status);
$this->token = $data['accessToken'];
$this->expiresAt = time() + $data['expiresIn'] - 120;
}
public function request($method, $path, $body = null, $retried = false) {
if (!$this->token || time() > $this->expiresAt) $this->login();
[$status, $data] = $this->call($method, $this->base . $path,
['Authorization: Bearer ' . $this->token, 'Content-Type: application/json'], $body);
if ($status === 401 && !$retried) { $this->token = null; return $this->request($method, $path, $body, true); }
if ($status >= 400) throw new Exception("$method $path → $status");
return $data;
}
}
$efrat = new Efrat();
$lancamentos = $efrat->request('GET', '/api/financial-entries?page=1&pageSize=50&type=Receber');TOKEN=$(curl -s -X POST "https://api.efrat.com.br/api/integration/token" \
-H "Content-Type: application/json" \
-d "{\"clientId\":\"$EFRAT_CLIENT_ID\",\"clientSecret\":\"$EFRAT_CLIENT_SECRET\"}" | jq -r .accessToken)
curl "https://api.efrat.com.br/api/financial-entries?page=1&pageSize=50&type=Receber" \
-H "Authorization: Bearer $TOKEN"Webhooks: receba os dados quando algo mudar
Em vez de consultar a API de tempos em tempos, você pode cadastrar um webhook: sempre que um registro de uma tela for
incluído, alterado ou excluído no Efrat (por um usuário ou por uma integração),
o Efrat envia um POST com os dados para o endereço que você informar.
Cadastrando um webhook
- No Efrat, abra o módulo Sistema → menu Configurações → WebHook e clique em Novo.
- Dê um Nome (ex.: Novos lançamentos para o ERP).
- Escolha a Rota (a tela, ex.: Financeiro › Lançamentos) e o Gatilho: Created (inclusão), Updated (alteração) ou Deleted (exclusão). Cada webhook escuta um gatilho; para receber os três, cadastre três webhooks.
- Informe a URL de destino — um endereço
https://do seu sistema que aceitePOST. - Salve e copie o Secret exibido. Ele serve para conferir a assinatura de cada envio e só é mostrado agora (pode ser regenerado depois).
O que o Efrat envia
POST https://seu-sistema.com.br/webhooks/efrat
Content-Type: application/json
X-Webhook-Id: 6a1f0c2e-9a7b-4d0e-8f53-1c2b3d4e5f60
X-Webhook-Event: CENTRO_CUSTO.Created
X-Webhook-Attempt: 1
X-Webhook-Signature: sha256=5d41402abc4b2a76b9719d911017c592...
{
"event": "CENTRO_CUSTO.Created",
"entity": "CENTRO_CUSTO",
"trigger": "Created",
"method": "POST",
"path": "/api/cost-centers",
"occurredAtUtc": "2026-10-20T14:32:05.123Z",
"data": {
"id": "922fabe3-772b-49aa-a3d4-8f149cd5e2b5",
"code": "0010",
"description": "Marketing",
"status": true
}
}
| Campo / cabeçalho | Descrição |
|---|---|
event | TELA.Gatilho — ex.: LANCAMENTO.Updated. |
entity | Código da tela (o mesmo dos escopos). |
trigger | Created, Updated ou Deleted. |
method / path | A rota do Efrat que gerou o evento. |
occurredAtUtc | Quando aconteceu (UTC). |
data | O mesmo JSON que a rota devolveu — normalmente o registro completo. Em exclusões, traz o identificador do registro excluído (ex.: {"id": "..."}). |
X-Webhook-Id | Identificador do webhook cadastrado. |
X-Webhook-Attempt | Número da tentativa (1, 2 ou 3). |
X-Webhook-Signature | sha256= + assinatura HMAC-SHA256 do corpo com o seu Secret (veja abaixo). |
Respondendo ao Efrat
- Responda com qualquer código
2xx(ex.:200) para confirmar o recebimento. - Responda rápido: processe os dados depois (fila), sem segurar a resposta.
- Se a entrega falhar (erro de rede ou resposta diferente de
2xx), o Efrat tenta de novo após 5 segundos e após 30 segundos — no máximo 3 tentativas. Por isso, trate o mesmo evento chegando mais de uma vez (useevent+data.id+occurredAtUtcpara descartar repetidos). - O histórico de entregas (sucesso, código de resposta e trecho do retorno) aparece no cadastro do webhook, no Efrat.
Validando a assinatura do webhook
Para ter certeza de que a chamada veio do Efrat, calcule o HMAC-SHA256 do corpo bruto recebido (exatamente os
bytes recebidos, antes de converter o JSON) usando o Secret do webhook, em hexadecimal minúsculo, e compare com o valor depois de
sha256= no cabeçalho X-Webhook-Signature. Se não bater, recuse com 401.
// Express
import express from 'express';
import crypto from 'node:crypto';
const app = express();
app.post('/webhooks/efrat', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', process.env.EFRAT_WEBHOOK_SECRET).update(req.body).digest('hex');
const received = req.get('X-Webhook-Signature') || '';
const ok = received.length === expected.length && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!ok) return res.sendStatus(401);
const event = JSON.parse(req.body.toString('utf8'));
res.sendStatus(200); // responde já
processarDepois(event); // e processa em seguida
});# Flask
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/webhooks/efrat")
def efrat_webhook():
raw = request.get_data()
expected = "sha256=" + hmac.new(os.environ["EFRAT_WEBHOOK_SECRET"].encode(), raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Webhook-Signature", "")):
abort(401)
event = request.get_json()
# enfileire event para processar depois
return "", 200// ASP.NET Core (minimal API)
app.MapPost("/webhooks/efrat", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var raw = await reader.ReadToEndAsync();
var secret = Environment.GetEnvironmentVariable("EFRAT_WEBHOOK_SECRET")!;
using var hmac = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(secret));
var expected = "sha256=" + Convert.ToHexStringLower(hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(raw)));
var received = request.Headers["X-Webhook-Signature"].ToString();
if (!System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(
System.Text.Encoding.UTF8.GetBytes(expected), System.Text.Encoding.UTF8.GetBytes(received)))
return Results.Unauthorized();
var evt = System.Text.Json.JsonDocument.Parse(raw);
// enfileire evt para processar depois
return Results.Ok();
});<?php
$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, getenv('EFRAT_WEBHOOK_SECRET'));
$received = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit;
}
$event = json_decode($raw, true);
// enfileire $event para processar depois
http_response_code(200);# Conferindo uma assinatura na linha de comando
echo -n '{"event":"CENTRO_CUSTO.Created", ...corpo exato...}' \
| openssl dgst -sha256 -hmac "SEU_WEBHOOK_SECRET" | sed 's/^.* /sha256=/'Testando: use um serviço como webhook.site como URL de destino para ver exatamente o que o Efrat envia,
antes de apontar para o seu sistema.
Todas as rotas
Rotas disponíveis para clientes de integração, agrupadas por módulo e tela. Clique numa rota para ver os parâmetros, os campos
do corpo, um exemplo de resposta e o comando pronto. Todas exigem o cabeçalho Authorization: Bearer SEU_TOKEN.
Boas práticas
- Um cliente de integração por sistema, com o mínimo de escopos necessário.
- Guarde o Client Secret e o Secret do webhook só no servidor (variáveis de ambiente ou cofre de segredos).
- Reaproveite o token pelos 60 minutos de validade, em vez de fazer login a cada requisição.
- Prefira webhooks a consultas repetidas para saber o que mudou; use a API para buscar detalhes e para gravar.
- Pagine listas grandes e use os filtros de data/status para buscar só o necessário.
- Trate repetições de webhook (até 3 tentativas) e responda
2xxrapidamente. - Registre os erros com o corpo da resposta: a mensagem em
messageexplica o motivo. - Troque o secret (Gerar novo secret) se suspeitar de vazamento, e desative clientes que não usa mais.
Perguntas frequentes
Recebo 403 numa rota que deveria funcionar.
Confira se o escopo indicado na rota está marcado no cliente de integração, se a tela faz parte do plano contratado e se a assinatura está em dia. Depois de mudar os escopos, faça o login de novo para receber um token atualizado.
Recebo 401 de repente.
O token venceu (60 minutos) ou o cliente foi desativado/teve o secret trocado. Faça o login de novo.
Os registros gravados pela integração aparecem como feitos por quem?
Como Integração <nome do cliente> — assim fica claro no Efrat o que veio de cada sistema.
Posso usar o mesmo cliente de integração em dois sistemas?
Pode, mas não é recomendado: crie um para cada sistema, para controlar e revogar o acesso separadamente.
O webhook também dispara para alterações feitas pela própria integração?
Sim. Qualquer inclusão, alteração ou exclusão na tela dispara o webhook, venha de um usuário ou de uma integração.
Existe ambiente de testes?
Use um cliente de integração com escopo só de consulta para começar, e um endereço como webhook.site para inspecionar os webhooks.