Développeurs · version 2026-09-01

API et intégrations Fidélitas

Envoyez les ventes d'une caisse, reconnaissez les clients, créditez leurs points et recevez les événements de Fidélitas en temps réel. Une API REST en JSON, des webhooks signés et un format d'événements unique, quel que soit le logiciel d'origine.

Démarrer

  1. Le commerçant crée une clé dans Intégrations › API et webhooks et choisit ses droits (préréglage « Caisse » recommandé).
  2. Chaque requête porte l'en-tête Authorization: Bearer fidk_…. La clé désigne le commerce : aucun identifiant de commerce à transmettre.
  3. Adresse de base : https://fidelitas.fr/api/v1. Réponses JSON, dates ISO 8601, montants en centimes.

Droits d'une clé

  • customers:readLire les clients et leurs points
  • customers:writeCréer des clients
  • transactions:writeEnvoyer des ventes et remboursements
  • loyalty:writeEnregistrer des visites et ajuster des points
  • rewards:readLire les récompenses et le programme
  • rewards:writeUtiliser des récompenses
  • actions:runDéclencher les codes caisse (récompense, points, message)
  • campaigns:sendLancer des campagnes
  • webhooks:manageGérer les webhooks

Endpoints

Méthode et cheminRôleDroit
GET/customersLister / rechercher les clients (q, filter, page)customers:read
POST/customersCréer un client (consentement explicite)· Idempotency-Keycustomers:write
GET/customers/{id}Fiche d'un clientcustomers:read
GET/customers/{id}/pointsSolde, prochaine récompense, récompenses disponiblescustomers:read
POST/customers/{id}/visitsEnregistrer une visite· Idempotency-Keyloyalty:write
GET/customers/by-token/{cardToken}Retrouver un client depuis le QR de sa cartecustomers:read
POST/transactionsEnvoyer une vente (points, visite, rattachement au scan) ; Prefer: respond-async → 202 sans attendre le traitement· Idempotency-Keytransactions:write
POST/transactions/{saleId}/refundsRembourser une vente (points retirés)· Idempotency-Keytransactions:write
POST/loyalty/eventsPassage sans vente ou ajustement de points· Idempotency-Keyloyalty:write
GET/rewardsPaliers du programme et récompenses d'un client (customerId)rewards:read
POST/rewards/redeemUtiliser une récompense (code)· Idempotency-Keyrewards:write
GET/programRègles du programme (tampons / points, paliers)rewards:read
GET/pos/actionsCodes caisse configurés par le commerçantactions:run
POST/pos/actions/{code}Déclencher un code caisse pour un client (récompense, points, campagne, étiquette)· Idempotency-Keyactions:run
GET/campaignsCampagnes prêtes à partir (brouillons)campaigns:send
POST/campaigns/{id}/sendLancer une campagne (tout le groupe) ou l'envoyer à un seul client (customer)· Idempotency-Keycampaigns:send
GET/webhooksAdresses de webhookswebhooks:manage
POST/webhooksAjouter une adresse (secret renvoyé une seule fois)· Idempotency-Keywebhooks:manage
DELETE/webhooks/{id}Supprimer une adressewebhooks:manage

Envoyer une vente

Le client est désigné par son identifiant Fidélitas, le jeton de sa carte (QR scanné en caisse), son email ou son téléphone. Un client inconnu avec email ou téléphone est créé (sans accord marketing). La même vente envoyée deux fois ne compte qu'une fois.

Requête
curl https://fidelitas.fr/api/v1/transactions \
  -H "Authorization: Bearer fidk_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ticket-2026-000123" \
  -d '{
    "id": "ticket-2026-000123",
    "amountCents": 2450,
    "currency": "EUR",
    "customer": { "cardToken": "ckx…", "email": "lea@exemple.fr" },
    "items": [{ "name": "Coupe", "quantity": 1, "unitCents": 2450 }]
  }'
Réponse 201
{
  "eventId": "cm…",
  "duplicate": false,
  "status": "PROCESSED",
  "outcome": "visit_created",
  "outcomeLabel": "Visite enregistrée",
  "customerId": "cm…",
  "visitId": "cm…",
  "transactionId": "cm…",
  "points": 1,
  "error": null
}

Borne ou caisse en rush : n'attendez jamais Fidélitas. Ajoutez l'en-tête Prefer: respond-async : la vente est accusée en 202 dès sa réception (statut RECEIVED) et traitée juste après. Envoyez-la sans bloquer l'encaissement, avec un délai d'attente de 2 secondes et un rejeu avec la même Idempotency-Key en cas d'échec : jamais de double. Le résultat arrive par le webhook transaction.created et dans Intégrations › Journal. Indiquez terminalId (le nom du poste, comme sur la Caisse ou le Comptoir) pour qu'une vente sans client ne soit rattachée qu'à la carte scannée sur ce poste.

Remboursement : POST /transactions/ticket-2026-000123/refunds avec { "refundId": "r-1", "amountCents": 2450 }. Les points correspondants sont retirés (jamais en dessous de zéro).

Règles de fidélité appliquées

  • Si la carte du client a été scannée juste avant (30 minutes), le montant est rattaché à cette visite : pas de double comptage.
  • Plusieurs achats le même jour : une seule visite (carte à tampons : un seul tampon ; carte à points : les points s'ajoutent).
  • Vente sans client : rattachée à la seule carte scannée dans les minutes précédentes sur le même poste (terminalId), sinon enregistrée sans point (chiffre d'affaires connu). Le client peut aussi rattacher lui-même son ticket depuis sa carte, avec le numéro de commande (id de la vente).
  • La carte Wallet est mise à jour et les automatisations du commerçant se déclenchent comme pour une visite saisie en caisse.

Webhook « Autre caisse »

Pour une caisse qui sait appeler une adresse web (ou via Zapier / Make) : le commerçant crée une connexion « Autre caisse » et vous transmet une adresse et un secret. Envoyez un événement (ou { "events": [ … ] }, 100 au plus), signé.

Types d'événements

  • sale.completedVente
  • sale.refundedRemboursement
  • customer.upsertedClient créé ou modifié
  • customer.visitPassage
  • loyalty.adjustedPoints ajustés
Corps (sale.completed)
{
  "id": "evt-000123",
  "type": "sale.completed",
  "occurredAt": "2026-09-23T10:42:00Z",
  "data": {
    "saleId": "ticket-000123",
    "amountCents": 1250,
    "currency": "EUR",
    "customer": { "externalId": "C-981", "phone": "+33612345678", "firstName": "Léa" },
    "locationExternalId": "boutique-1"
  }
}
Signature (Node.js)
import crypto from "node:crypto";

const body = JSON.stringify(event);
const t = Math.floor(Date.now() / 1000);
const v1 = crypto.createHmac("sha256", SECRET).update(`${t}.${body}`).digest("hex");

await fetch(WEBHOOK_URL, {
  method: "POST",
  headers: { "Content-Type": "application/json", "Fidelitas-Signature": `t=${t},v1=${v1}` },
  body,
});

Réponse 202 avec le nombre d'événements acceptés et de doublons ignorés. Signature absente, fausse ou de plus de 5 minutes : 401. Format invalide : 422 avec le champ en cause. Le résultat de chaque événement est visible par le commerçant dans son journal.

Webhooks sortants

Fidélitas appelle vos adresses (https) à chaque événement. En cas d'échec (réponse autre que 2xx, ou pas de réponse en 10 secondes), nouvel essai après 1 min, 5 min, 30 min, 2 h, 6 h, 12 h puis 24 h. Une adresse en échec 50 fois de suite est mise en pause.

  • customer.createdNouveau client
  • customer.updatedClient modifié
  • visit.createdVisite enregistrée
  • transaction.createdVente reçue d'une caisse
  • points.addedPoints ajoutés
  • reward.unlockedRécompense débloquée
  • reward.redeemedRécompense utilisée
  • wallet.updatedCarte Wallet ajoutée
  • automation.triggeredMessage automatique envoyé
Vérifier la signature (Node.js)
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = Buffer.from(crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex"));
  const received = Buffer.from(parts.v1 ?? "");
  return received.length === expected.length && crypto.timingSafeEqual(expected, received);
}

En-têtes : Fidelitas-Signature, Fidelitas-Event-Id (identique à chaque nouvel essai : dédoublonnez avec), Fidelitas-Event-Type.

Erreurs, limites, idempotence

  • Erreurs au format { "error": "message lisible", "code": "VALIDATION_ERROR", "details": [ … ] } : 400 requête illisible, 401 clé invalide, 403 droit manquant (INSUFFICIENT_SCOPE), 402 abonnement inactif, 404 introuvable, 409 conflit, 422 données invalides, 429 limite atteinte.
  • 600 requêtes par minute et par commerce, toutes adresses confondues ; la création de client (120), les codes caisse (300), l'envoi de campagne et l'ajout de webhook (30) ont en plus leur propre limite. En-têtes X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset et Retry-After.
  • Idempotency-Key sur les écritures : la même clé avec la même requête renvoie la même réponse pendant 24 h (en-tête Idempotent-Replayed: true), sans effet en double ; avec une autre requête : 422.
  • Chaque réponse porte Fidelitas-Api-Version et X-Request-Id (à citer au support). Les changements incompatibles passeront par une nouvelle version, annoncée à l'avance.

Caisses connectées

Connexion en deux clics depuis l'espace commerçant, sans développement :

  • Squaretemps réel · client transmis
  • Shopify POStemps réel · client transmis
  • SumUptoutes les 15 minutes · rattachement à la carte scannée
  • Zettle (PayPal POS)temps réel · rattachement à la carte scannée
  • HubRisetemps réel · client transmis
  • Hiboutiktemps réel · client transmis

Éditeur de caisse et intéressé par un connecteur natif ? Écrivez-nous.