© 2026 quotery.io. Todos os direitos reservados
DHDTech.io · Sheridan, Wyoming
Desenvolvido por
DHDTech.io
Receba notificações push em tempo real quando coisas acontecem na sua conta Quotery. Sem polling, sem cron jobs. Apenas callbacks HTTP assinados entregues direto ao seu servidor.
Um webhook é uma notificação push que o Quotery envia ao seu servidor sempre que algo importante acontece. Um orçamento é aceito, uma nota de entrega é marcada como entregue ou uma nota de devolução é criada. Em vez de você perguntar "alguma novidade?" repetidamente (polling, um desperdício), nós te avisamos no momento em que o evento dispara.
Você nos informa uma URL e quais tipos de evento te interessam. Quando um desses eventos acontece na sua conta, enviamos um payload JSON assinado via POST para sua URL. Você verifica a assinatura para confirmar que veio de nós, processa os dados e retorna um 200. Só isso.
Webhooks são a espinha dorsal das integrações em tempo real. Use para enviar pedidos para seu ERP, sincronizar inventário com um sistema externo, disparar notificações no Slack quando um orçamento fecha ou criar um registro de auditoria de tudo que acontece no seu tenant. Você escolhe os eventos, você gerencia o receptor.
Cada tipo de evento representa uma ação de negócio específica dentro do Quotery. Você assina os que são relevantes para sua integração. Escolha alguns ou todos os sete.
quote.closed — Dispara quando um orçamento é fechado (finalizado e enviado ao cliente). O payload inclui os detalhes do orçamento, itens e totais no momento do fechamento.quote.accepted — Dispara quando um cliente aceita um orçamento pelo portal. É o evento principal para iniciar a expedição: crie um pedido no ERP, notifique o almoxarifado ou gere uma nota fiscal.quote.cancelled — Dispara quando um orçamento é cancelado. Use para desfazer processos: liberar inventário reservado, cancelar um pedido ou atualizar o estágio do negócio no CRM.delivery_note.created — Dispara quando uma nota de entrega é criada. Se você acompanha envios num sistema externo, este evento avisa que uma nova entrega está sendo preparada.delivery_note.marked_delivered — Dispara quando uma nota de entrega é marcada como entregue. É a confirmação de que as mercadorias chegaram. Use para reduzir inventário, fechar uma tarefa de expedição ou disparar uma fatura.stock_receipt.completed — Dispara quando um recebimento de estoque é concluído. As mercadorias recebidas agora estão no seu inventário. Sincronize com seu WMS ou atualize níveis de estoque num catálogo externo.return_note.created — Dispara quando uma nota de devolução é criada. Um cliente está devolvendo algo. Você vai querer ajustar inventário, iniciar um fluxo de reembolso ou registrar a devolução no seu sistema de pedidos.Configurar um webhook leva cerca de um minuto. Você precisa de acesso admin à sua conta Quotery. O gerenciamento de webhooks é uma função administrativa.
O segredo de assinatura é gerado automaticamente quando você cria o webhook. Você não o escolhe, você o recebe. E você só o vê uma vez, logo após a criação. Copie para um lugar seguro antes de fechar aquela aba de resposta.
Você precisa estar logado como admin para criar ou gerenciar webhooks. Se não for admin, procure alguém da sua equipe que seja.
Cada entrega de webhook inclui uma assinatura criptográfica para você confirmar que o payload veio do Quotery e não foi adulterado. Verificar é obrigatório. Nunca processe um payload de webhook sem verificar a assinatura primeiro.
Nós assinamos o corpo bruto da requisição com HMAC-SHA256 usando o segredo de assinatura único do seu webhook. A assinatura vai no cabeçalho X-Quotery-Signature num formato compatível com o esquema do Stripe. Se você já trabalhou com webhooks do Stripe, vai achar familiar.
O cabeçalho X-Quotery-Signature se parece com isto:
t=1714789200,v1=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2import hmac, hashlib, time
def verify_signature(body: bytes, signature_header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in signature_header.split(","))
timestamp = int(parts["t"])
if abs(time.time() - timestamp) > tolerance:
return False # outside tolerance window
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(parts["v1"], expected)O parâmetro de tolerância (padrão 300 segundos = 5 minutos) rejeita entregas antigas. É sua proteção contra ataques de replay. Ajuste se seus relógios estiverem dessincronizados, mas não defina acima de 5 minutos.
const crypto = require("crypto");
function verifySignature(rawBody, signatureHeader, secret, tolerance = 300) {
// Parse "t=1714789200,v1=abc123..." into { t, v1 }
const parts = {};
signatureHeader.split(",").forEach(p => {
const idx = p.indexOf("=");
parts[p.slice(0, idx)] = p.slice(idx + 1);
});
const timestamp = parseInt(parts.t, 10);
const now = Math.floor(Date.now() / 1000);
// Reject if outside tolerance window (5 minutes)
if (Math.abs(now - timestamp) > tolerance) {
return false;
}
// Compute the expected signature
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
// Constant-time comparison prevents timing attacks
return crypto.timingSafeEqual(
Buffer.from(parts.v1),
Buffer.from(expected)
);
}A verificação de assinatura usa timingSafeEqual em vez de comparação de strings comum. Isso é importante. Operadores de igualdade comuns causam curto-circuito, vazando informação de tempo sobre quanto da assinatura coincidiu. Sempre use comparação de tempo constante para verificação HMAC.
Cada entrega POST inclui estes quatro cabeçalhos. Você os usa para verificação de assinatura, idempotência e decisões de roteamento.
| Cabeçalho | Valor |
|---|---|
| Content-Type | application/json |
| X-Quotery-Webhook-Id | UUID (ex. 550e8400-e29b-41d4-a716-446655440000) |
| X-Quotery-Event | Tipo de evento separado por ponto (ex. quote.closed) |
| X-Quotery-Signature | t=1714789200,v1=abc123... |
Content-Type — O corpo da requisição é sempre JSON. Seu endpoint pode assumir isso e fazer o parsing.
X-Quotery-Webhook-Id — Um identificador único para esta tentativa de entrega. Use como chave de idempotência. Se uma entrega for reenviada, carrega o mesmo ID. Você deduplica verificando se já processou este ID.
X-Quotery-Event — Informa exatamente qual evento disparou. Use num switch para rotear eventos diferentes para manipuladores diferentes.
X-Quotery-Signature — A assinatura HMAC-SHA256. Faça o parsing, verifique e só processe o payload se for válida.
Se seu endpoint estiver fora do ar ou retornar um status não-2xx, o Quotery reenvia a entrega automaticamente. Você não precisa configurar nada. A agenda de retentativas já vem embutida.
Entregas com falha são reenviadas até 6 vezes usando backoff exponencial. Esta é a linha do tempo a partir da tentativa inicial:
A janela total de retentativas cobre cerca de 12 horas. Após 6 tentativas com falha, a entrega é descartada permanentemente.
Cada webhook rastreia dois indicadores: o timestamp da última entrega bem-sucedida e uma contagem de falhas consecutivas. Uma entrega bem-sucedida zera a contagem. Uma sequência de falhas a incrementa. Você verifica ambos a qualquer momento pela API. Sem logs para vasculhar, sem alertas para configurar. Se a contagem de falhas estiver subindo, seu endpoint provavelmente está fora do ar ou rejeitando entregas.
Como as retentativas reutilizam o mesmo X-Quotery-Webhook-Id, seu endpoint pode lidar com segurança com entregas duplicadas. Mantenha um pequeno cache de IDs de webhook processados recentemente (algumas horas bastam, considerando a janela de 12 horas) e pule qualquer entrega cujo ID você já tenha visto.
Cada entrega de webhook carrega o mesmo envelope externo. Os detalhes específicos do evento ficam dentro do objeto data.
{
"event_type": "quote.closed",
"tenant_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2026-05-03T10:00:00Z",
"data": {
// Payload específico do evento — varia por event_type
}
}Você não precisa de um ambiente de staging para testar webhooks. Pode disparar eventos reais na sua conta Quotery e vê-los chegando ao seu endpoint em tempo real.
Pronto para conectar? Veja as duas tarefas mais comuns (criar um webhook e receber entregas) em formato para copiar e colar.
Este comando curl cria um webhook que escuta os eventos quote.accepted, quote.closed e delivery_note.marked_delivered. Você precisa do cookie de sessão de uma sessão autenticada no navegador como admin.
curl -X POST https://app.quotery.io/api/v1/webhooks/ \
-H "Content-Type: application/json" \
-H "Cookie: sessionid=<seu-id-de-sessao-admin>" \
-d '{
"name": "Minha Integração",
"url": "https://meuapp.exemplo.com/webhooks/quotery",
"events": ["quote.accepted", "quote.closed", "delivery_note.marked_delivered"]
}'A resposta 201 inclui o ID do webhook e, ponto crítico, o signing_secret. Copie esse segredo agora. Você não o verá novamente.
Aqui está um endpoint Express mínimo que verifica a assinatura, confere a tolerância de timestamp e deduplica por ID de webhook. Incorpore no seu app Node existente ou use como ponto de partida.
const express = require("express");
const crypto = require("crypto");
const app = express();
const SECRET = process.env.QUOTERY_WEBHOOK_SECRET;
const TOLERANCE = 300; // 5 minutos em segundos
// Armazenamento de idempotência em memória — use Redis ou um BD em produção
const processedIds = new Set();
app.post("/webhooks/quotery", express.json({
verify: (req, _res, buf) => { req.rawBody = buf; }
}), (req, res) => {
// 1. Fazer parsing do cabeçalho de assinatura
const header = req.headers["x-quotery-signature"];
if (!header) return res.status(400).send("Assinatura ausente");
const parts = {};
header.split(",").forEach(p => {
const idx = p.indexOf("=");
parts[p.slice(0, idx)] = p.slice(idx + 1);
});
// 2. Rejeitar se o timestamp estiver fora da tolerância
const timestamp = parseInt(parts.t, 10);
if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > TOLERANCE) {
return res.status(400).send("Timestamp fora da tolerância");
}
// 3. Verificar a assinatura HMAC
const expected = crypto
.createHmac("sha256", SECRET)
.update(req.rawBody)
.digest("hex");
const valid = crypto.timingSafeEqual(
Buffer.from(parts.v1),
Buffer.from(expected)
);
if (!valid) return res.status(400).send("Assinatura inválida");
// 4. Deduplicar por ID de webhook
const deliveryId = req.headers["x-quotery-webhook-id"];
if (processedIds.has(deliveryId)) {
return res.sendStatus(200); // Já processado — confirmar silenciosamente
}
processedIds.add(deliveryId);
// 5. Processar o evento
const eventType = req.headers["x-quotery-event"];
const payload = req.body;
console.log(`Recebido ${eventType}`, payload);
switch (eventType) {
case "quote.accepted":
// Criar um pedido no seu ERP
break;
case "quote.closed":
// Atualizar o estágio do negócio no seu CRM
break;
case "delivery_note.marked_delivered":
// Reduzir inventário, disparar fatura
break;
default:
console.log(`Tipo de evento não tratado: ${eventType}`);
}
res.sendStatus(200);
});
app.listen(process.env.PORT || 3000, () => {
console.log("Receptor de webhook escutando");
});A verificação de idempotência na etapa 4 é importante. O Quotery reenvia entregas até 6 vezes e cada retentativa carrega o mesmo X-Quotery-Webhook-Id. Sem deduplicação, você processaria o mesmo evento várias vezes: pedidos duplicados, contagens de inventário em dobro e por aí vai.