3eyetlia

API caisse

Votre logiciel de caisse crée le numéro du client avec le n° de facture, puis le marque « prêt » : le téléphone du client sonne, l'écran TV l'annonce. Aucune intégration n'est nécessaire pour commencer (le QR du comptoir suffit).

1. Authentification

Dans Tableau de bord → 5. API, cliquez « Khle9 clé API ». La clé (3ey_…) n'est affichée qu'une fois. Envoyez-la dans chaque requête, côté serveur uniquement :

Authorization: Bearer 3ey_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

2. Créer un numéro

external_ref (facultatif, 1–64 caractères) = votre n° de facture. Si un ticket en cours a déjà cette référence, il est renvoyé tel quel (created: false) : vous pouvez réessayer sans créer de doublon.

curl -X POST https://3eyetlia.com/api/v1/tickets \
  -H "Authorization: Bearer $CLE" -H "Content-Type: application/json" \
  -d '{"external_ref": "F-1042"}'

→ 201
{ "created": true,
  "data": { "id": "…", "number": 27, "status": "waiting", "external_ref": "F-1042",
            "url": "https://3eyetlia.com/t/…", … } }

Donnez data.url au client (QR imprimé sur le ticket de caisse, SMS, WhatsApp…) : c'est sa page avec son numéro et le bouton d'alerte.

3. Changer le statut

{ref} = l'id du ticket ou votre external_ref.

POST /api/v1/tickets/{ref}/ready    # prêt : le téléphone sonne
POST /api/v1/tickets/{ref}/undo     # annuler « prêt » (erreur)
POST /api/v1/tickets/{ref}/done     # remis au client
POST /api/v1/tickets/{ref}/cancel   # annulé

curl -X POST https://3eyetlia.com/api/v1/tickets/F-1042/ready -H "Authorization: Bearer $CLE"
GET /api/v1/tickets          # tickets en cours (waiting / ready)
GET /api/v1/tickets/{ref}    # un ticket

4. Webhooks

Indiquez une URL https dans le tableau de bord. Nous envoyons un POST JSON à chaque événement :

{ "id": "6f1c…", "event": "ticket.ready", "created_at": "2026-10-14T12:00:00Z",
  "data": { "id": "…", "number": 27, "status": "ready", "external_ref": "F-1042", … } }

Vérifiez la signature avec le secret affiché dans le tableau de bord (whsec_…). En-tête : X-3eyetlia-Signature: t=<horodatage>,v1=<hmac>, où hmac = HMAC-SHA256(secret, t + "." + corps brut).

// Node.js : refuse proprement un en-tête absent, malformé ou trop vieux, sans jamais lever d'exception
const crypto = require("crypto");
function verify(rawBody, header, secret) {
  if (typeof header !== "string" || header.length > 300) return false;
  const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=", 2)).filter((kv) => kv.length === 2));
  const t = Number(parts.t), v1 = parts.v1;
  if (!Number.isInteger(t) || !/^[0-9a-f]{64}$/.test(v1 || "")) return false;
  if (Math.abs(Date.now() / 1000 - t) > 300) return false; // 5 min max (anti-rejeu)
  const expected = crypto.createHmac("sha256", secret).update(t + "." + rawBody).digest();
  const got = Buffer.from(v1, "hex");
  return got.length === expected.length && crypto.timingSafeEqual(expected, got);
}

Délai de réponse : 5 s max, répondez 2xx. Les redirections ne sont pas suivies.

Livraison au moins une fois : en cas d'échec, le même événement (même id, en-tête X-3eyetlia-Event-Id) est renvoyé après 1 min, 5 min, 30 min, 2 h puis 12 h. Ignorez un id déjà traité. L'ordre d'arrivée n'est pas garanti : fiez-vous à data.status et à created_at. Les démos n'envoient jamais de webhooks.

5. Erreurs et limites

{ "error": { "code": "not_found", "message": "Ticket introuvable" } }

401 unauthorized · 400 invalid_external_ref / invalid_action · 404 not_found
409 invalid_transition (ex. « ready » sur un ticket déjà servi ou annulé)
429 rate_limited (60 nouveaux numéros / minute / commerce) · 500 server_error

Transitions : ready depuis waiting · undo depuis ready · done et cancel depuis waiting ou ready. done et cancelled sont définitifs. Refaire la même action renvoie 200 avec "unchanged": true, sans nouvelle notification ni webhook.

Les numéros repartent de 1 chaque jour à 5 h (heure locale du commerce).