Início rápido
Do zero a um webhook verificado em cinco minutos, tudo no ambiente de teste.
Neste guia, você cria uma chave de teste, confirma a qual conta ela pertence, cadastra um cliente, monta um orçamento para ele, envia esse orçamento e recebe os avisos do Cord no seu localhost com a assinatura verificada. Nada do que você fizer aqui toca os seus dados reais: uma chave sk_test_ trabalha sobre o ambiente de teste da sua conta.
O e-mail de teste é enviado de verdade
Enviar um orçamento de teste manda um e-mail real para o cliente, com o assunto marcado como teste. Use um e-mail seu na etapa 4.
-
Crie uma chave de teste. No app, ative o Modo desenvolvedor em Sua conta › Preferências e abra a pílula Desenvolvedores no canto inferior direito. Na aba Chaves, crie uma Secret Key de leitura e escrita no modo de teste (se o app já estiver no modo de teste, a chave já nasce de teste). Ela começa com
sk_test_e é mostrada uma única vez: copie-a.O app no ambiente de teste com o Workbench aberto na aba Chaves: a chave secreta de leitura e escrita recém-criada aparece completa uma única vez, sk_test_ seguida de 48 caracteres, com o botão Copiar; atrás, a nova linha já aparece na tabela como Teste e Secreta. Guarde-a em uma variável do seu terminal. A CLI do Cord também a lê dali:
export CORD_API_KEY="sk_test_..." -
Confirme a chave.
GET /api/v1/meresponde com a organização, a permissão e o modo da chave:curl https://cordhq.app/api/v1/me \ -H "Authorization: Bearer $CORD_API_KEY"{ "data": { "org": { "id": "5b1f7d9e-3c2a-4e8b-9f61-0a7c4d2e8b13", "nombre": "Aceros del Norte", "plan": "pro" }, "scope": "write", "mode": "test" } }Se você vir
"mode": "test", tudo o que vem a seguir acontece no seu ambiente de teste. Um401comcode: "invalid_key"significa que a chave foi copiada errada ou foi revogada. -
Escute os webhooks na sua máquina. O Cord não consegue chamar o seu
localhost, então a CLI abre uma sessão de escuta e reencaminha cada evento para você com os headers de assinatura reais. Primeiro, o servidor que os recebe. Instale o SDK em uma pasta vazia:npm init -y && npm install @flouviahq/nodeSalve isto como
server.mjs:import { createServer } from 'node:http'; import { constructEvent, CordWebhookSignatureError } from '@flouviahq/node'; createServer(async (req, res) => { if (req.method !== 'POST' || req.url !== '/webhooks/cord') { res.writeHead(404).end(); return; } // A assinatura é calculada sobre o corpo BRUTO: não faça o parse dele antes de verificar. const chunks = []; for await (const chunk of req) chunks.push(chunk); try { const event = await constructEvent(Buffer.concat(chunks), req.headers, process.env.CORD_WEBHOOK_SECRET); console.log(event.event, event.id, event.data.id); res.writeHead(200).end('ok'); } catch (err) { if (err instanceof CordWebhookSignatureError) { console.error('Signature rejected:', err.code); res.writeHead(400).end(err.code); return; } throw err; } }).listen(3000, () => console.log('Listening on http://localhost:3000/webhooks/cord'));Em um segundo terminal (com a mesma
CORD_API_KEY), abra a sessão de escuta:npx @flouviahq/cli listen --forward-to http://localhost:3000/webhooks/cordA CLI imprime o segredo da sessão (
whsec_…). Inicie o seu servidor com ele no primeiro terminal:CORD_WEBHOOK_SECRET="whsec_..." node server.mjsDeixe os dois rodando. A sessão expira em 24 horas e Ctrl+C a encerra.
-
Crie um cliente. Com um e-mail seu:
curl https://cordhq.app/api/v1/clientes \ -H "Authorization: Bearer $CORD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "empresa": "Ferretería La Prueba", "contacto": "Ana Torres", "email": "your-email@example.com" }'{ "data": { "id": "0e6f2b8a-91c4-4d7e-b3a5-6c2d8f1e4a90" } }O seu servidor imprime o primeiro evento:
client.created evt_4c1e9a7b2d5f8e0a3b6c9d2e5f8a1b4c 0e6f2b8a-91c4-4d7e-b3a5-6c2d8f1e4a90 -
Crie o orçamento. Use o
iddo cliente. Semtax_rate, cada item usa a alíquota padrão do catálogo de impostos da sua conta; sembase_currency, o orçamento fica na moeda da sua conta.curl https://cordhq.app/api/v1/cotizaciones \ -H "Authorization: Bearer $CORD_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 6a1d3f5e-8b2c-4e7a-9d0f-1c3e5a7b9d2f" \ -d '{ "cliente_id": "0e6f2b8a-91c4-4d7e-b3a5-6c2d8f1e4a90", "items": [ { "descripcion": "Window frame installation", "cantidad": 1, "precio_unitario": 12000 } ], "vigencia_dias": 15 }'{ "data": { "id": "8b0c6f0e-2d55-4c1e-9a43-6a2f1c7d9e10", "token": "3f9a1c2e7b4d6f8a0b1c2d3e4f5a6b7c", "folio": "COT-0001", "link_publico": "https://cordhq.app/q/3f9a1c2e7b4d6f8a0b1c2d3e4f5a6b7c", "needs_approval": false, "motivo": null } }O orçamento nasce como rascunho e o seu servidor recebe
quote.created. O headerIdempotency-Keytorna seguro tentar de novo: com a mesma chave e o mesmo corpo, o Cord devolve esta mesma resposta em vez de criar um segundo orçamento. Gere uma chave nova (por exemplo, comuuidgen) para cada orçamento diferente. -
Envie-o. O envio gera o link e manda o e-mail para o cliente:
curl https://cordhq.app/api/v1/cotizaciones/8b0c6f0e-2d55-4c1e-9a43-6a2f1c7d9e10 \ -H "Authorization: Bearer $CORD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action": "send" }'{ "data": { "ok": true, "status": "sent", "email": { "sent": true } } }Se o cliente não tivesse e-mail,
emailchegaria como{ "sent": false, "skipped": "el cliente no tiene correo" }e o orçamento ficaria enviado do mesmo jeito. O seu servidor recebe:quote.sent evt_9d2a6c4e1f8b3a5d7c0e2f4a6b8d1c3e 8b0c6f0e-2d55-4c1e-9a43-6a2f1c7d9e10 -
Simule o cliente. Abra o link a partir do e-mail ou provoque a abertura com um simulador. Em outro terminal:
npx @flouviahq/cli simulate quote 8b0c6f0e-2d55-4c1e-9a43-6a2f1c7d9e10 vistaChega
quote.viewed. Para fechar o ciclo, aprove-o pela API ({ "action": "approve" }) e chegaráquote.approved.
O que cada aviso contém
Cada entrega é um POST com este corpo. id é a identidade do evento (igual em cada nova tentativa: faça a deduplicação com ele) e data é o resumo do orçamento:
{
"id": "evt_9d2a6c4e1f8b3a5d7c0e2f4a6b8d1c3e",
"event": "quote.sent",
"created_at": "2026-10-07T17:04:12.318Z",
"data": {
"id": "8b0c6f0e-2d55-4c1e-9a43-6a2f1c7d9e10",
"folio": "COT-0001",
"status": "sent",
"moneda": "MXN",
"total": 13920,
"cliente": "Ferretería La Prueba",
"cliente_id": "0e6f2b8a-91c4-4d7e-b3a5-6c2d8f1e4a90",
"link_publico": "https://cordhq.app/q/3f9a1c2e7b4d6f8a0b1c2d3e4f5a6b7c"
}
}
O total deste exemplo inclui o IVA de 16% de uma conta mexicana; na sua conta, ele sairá com as suas alíquotas. Todo valor viaja com a sua moneda (moeda).
O mesmo com o SDK de Node
import { Cord } from '@flouviahq/node';
const cord = new Cord(process.env.CORD_API_KEY);
const me = await cord.me();
const cliente = await cord.clients.create({ empresa: 'Ferretería La Prueba', email: 'your-email@example.com' });
const cotizacion = await cord.quotes.create({
cliente_id: cliente.id,
items: [{ descripcion: 'Window frame installation', cantidad: 1, precio_unitario: 12000 }],
});
await cord.quotes.send(cotizacion.id);
console.log(me.mode, cotizacion.folio, cotizacion.link_publico);
O SDK envia uma Idempotency-Key em cada operação de escrita e a repete nas suas novas tentativas. Veja SDK de Node.
Próximos passos
- Webhooks: cadastre um endpoint real, novas tentativas, rotação do segredo e verificação manual da assinatura.
- Eventos: os 41 tipos de evento e o histórico pela API.
- API de Orçamentos: aprovar, recusar, marcar como pago e excluir.
- Tratamento de erros: todos os códigos e quando tentar de novo.
- Antes de ir para produção, crie uma chave
sk_live_e cadastre o seu endpoint no Workbench: o segredo decord listensó serve para a sessão.