Pular para o conteúdo

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.

  1. 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_..."
  2. Confirme a chave. GET /api/v1/me responde 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. Um 401 com code: "invalid_key" significa que a chave foi copiada errada ou foi revogada.

  3. 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/node

    Salve 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/cord

    A CLI imprime o segredo da sessão (whsec_…). Inicie o seu servidor com ele no primeiro terminal:

    CORD_WEBHOOK_SECRET="whsec_..." node server.mjs

    Deixe os dois rodando. A sessão expira em 24 horas e Ctrl+C a encerra.

  4. 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
  5. Crie o orçamento. Use o id do cliente. Sem tax_rate, cada item usa a alíquota padrão do catálogo de impostos da sua conta; sem base_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 header Idempotency-Key torna 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, com uuidgen) para cada orçamento diferente.

  6. 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, email chegaria 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
  7. 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 vista

    Chega 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 de cord listen só serve para a sessão.