Démarrage rapide
De zéro à un webhook vérifié en cinq minutes, entièrement dans l’environnement de test.
Dans ce guide, vous créez une clé de test, vérifiez à quel compte elle appartient, ajoutez un client, lui préparez un devis, le lui envoyez et recevez les notifications de Cord sur votre localhost avec la signature vérifiée. Rien de ce que vous faites ici ne touche vos données réelles : une clé sk_test_ travaille sur l’environnement de test de votre compte.
L’e-mail de test part bel et bien
Envoyer un devis de test adresse un vrai e-mail au client, avec l’objet marqué comme test. Utilisez une adresse e-mail à vous à l’étape 4.
-
Créez une clé de test. Dans l’application, activez le Mode développeur dans Votre compte › Préférences et ouvrez la pastille Développeurs dans le coin inférieur droit. Dans l’onglet Clés API, créez une clé Secrète en lecture et écriture en mode test (si l’application est déjà en mode test, la clé est créée comme clé de test). Elle commence par
sk_test_et ne s’affiche qu’une seule fois : copiez-la.L’application dans l’environnement de test avec le Workbench ouvert sur l’onglet Clés API : la clé secrète en lecture et écriture qui vient d’être créée s’affiche en entier une seule fois, sk_test_ suivi de 48 caractères, avec son bouton Copier ; derrière, la nouvelle ligne figure déjà dans le tableau comme Test et Secrète. Enregistrez-la dans une variable de votre terminal. La CLI de Cord la lit aussi à cet endroit :
export CORD_API_KEY="sk_test_..." -
Vérifiez la clé.
GET /api/v1/merenvoie l’organisation, le droit et le mode de la clé :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" } }Si vous voyez
"mode": "test", tout ce qui suit se passe dans votre environnement de test. Un401aveccode: "invalid_key"signifie que la clé a été mal copiée ou qu’elle a été révoquée. -
Écoutez les webhooks sur votre machine. Cord ne peut pas appeler votre
localhost: la CLI ouvre donc une session d’écoute et vous retransmet chaque événement avec les vrais en-têtes de signature. D’abord, le serveur qui les reçoit. Installez le SDK dans un dossier vide :npm init -y && npm install @flouviahq/nodeEnregistrez ceci sous le nom
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; } // La signature est calculée sur le corps BRUT : ne l’analysez pas avant de la vérifier. 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'));Dans un second terminal (avec la même
CORD_API_KEY), ouvrez la session d’écoute :npx @flouviahq/cli listen --forward-to http://localhost:3000/webhooks/cordLa CLI affiche le secret de la session (
whsec_…). Démarrez votre serveur avec ce secret dans le premier terminal :CORD_WEBHOOK_SECRET="whsec_..." node server.mjsLaissez les deux tourner. La session expire au bout de 24 heures et Ctrl+C la ferme.
-
Créez un client. Avec une adresse e-mail à vous :
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" } }Votre serveur affiche le premier événement :
client.created evt_4c1e9a7b2d5f8e0a3b6c9d2e5f8a1b4c 0e6f2b8a-91c4-4d7e-b3a5-6c2d8f1e4a90 -
Créez le devis. Utilisez l’
iddu client. Sanstax_rate, chaque ligne prend le taux par défaut du catalogue de taxes de votre compte ; sansbase_currency, le devis est établi dans la devise de votre compte.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 } }Le devis naît à l’état de brouillon et votre serveur reçoit
quote.created. L’en-têteIdempotency-Keyrend les nouvelles tentatives sûres : avec la même clé et le même corps, Cord renvoie cette même réponse au lieu de créer un second devis. Générez une nouvelle clé (par exemple avecuuidgen) pour chaque devis différent. -
Envoyez-le. L’envoi génère le lien et adresse l’e-mail au client :
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 } } }Si le client n’avait pas d’adresse e-mail,
emailarriverait sous la forme{ "sent": false, "skipped": "el cliente no tiene correo" }et le devis serait tout de même envoyé. Votre serveur reçoit :quote.sent evt_9d2a6c4e1f8b3a5d7c0e2f4a6b8d1c3e 8b0c6f0e-2d55-4c1e-9a43-6a2f1c7d9e10 -
Simulez le client. Ouvrez le lien depuis l’e-mail ou déclenchez la consultation avec un simulateur. Dans un autre terminal :
npx @flouviahq/cli simulate quote 8b0c6f0e-2d55-4c1e-9a43-6a2f1c7d9e10 vistaquote.viewedarrive. Pour boucler le cycle, approuvez-le depuis l’API ({ "action": "approve" }) etquote.approvedarrivera.
Ce que contient chaque notification
Chaque envoi est un POST avec ce corps. id est l’identité de l’événement (identique à chaque nouvelle tentative : dédupliquez avec lui) et data est le résumé du devis :
{
"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"
}
}
Le total de cet exemple inclut l’IVA de 16 % d’un compte mexicain ; sur votre compte, il sera calculé avec vos taux. Tout montant voyage avec sa moneda (devise).
La même chose avec le SDK 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);
Le SDK envoie une Idempotency-Key à chaque opération d’écriture et la répète lors de ses nouvelles tentatives. Voir SDK Node.
Étapes suivantes
- Webhooks : enregistrez un vrai endpoint ; nouvelles tentatives, renouvellement du secret et vérification manuelle de la signature.
- Événements : les 41 types d’événements et l’historique par API.
- API des devis : approuver, refuser, marquer comme payé et supprimer.
- Gestion des erreurs : tous les codes et quand réessayer.
- Avant de passer en production, créez une clé
sk_live_et enregistrez votre endpoint dans le Workbench : le secret decord listenne sert que pour sa session.