Référence de l’API Walaora
Connectez une caisse, une boutique en ligne ou un système interne. JSON sur HTTPS, une clé API par intégration.
Authentification
Créez une clé dans Tableau de bord → API et webhooks (formule Business ou Entreprise). Envoyez-la avec chaque requête. Les clés ne s’affichent qu’une seule fois ; révoquez-les si elles sont exposées. Limite : 120 requêtes par minute et par clé.
Authorization: Bearer wl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxPoints de terminaison
Base URL: https://walaora.com/api/v1
| GET | /programsLister les programmes de fidélité. |
| GET | /customers?phone=0551234567&tag=vipRechercher des clients (50 par page, ?page=2…). ?tag=vip liste les clients ayant cette étiquette ; chaque client inclut ses étiquettes. |
| POST | /customersInscrire un client à un programme. Corps : { programId, name, phone, email?, marketingConsent?, locale? }. Renvoie 201 en cas de création, 200 si le client est déjà inscrit. |
| GET | /cards/{serial}Carte à partir du code figurant sur la carte / le QR du client. |
| POST | /cards/{serial}/earnProgrammes à tampons : { stamps?: 1–10 }. Programmes à points : { amount } dans la devise de l’espace de travail. Optionnel : { branchId }. Cartes multi-tampons : envoyez setId, l’une des séries de la carte. |
| POST | /cards/{serial}/redeemÉchanger une récompense disponible, ou envoyer {"rewardId"} pour échanger une récompense du catalogue dont le prix est en tampons/points. |
| POST | /cards/{serial}/sellForfaits prépayés : vend un forfait dont les utilisations s’ajoutent à la carte (paiement à votre caisse). Webhook : package.sold. |
| POST | /cards/{serial}/useForfaits prépayés : décompte une utilisation (refusé si vide ou expiré). Cartes employé : enregistre l’avantage dans sa limite quotidienne. Webhooks : package.used, perk.used. |
| GET | /coupons/{code}Vérifie un coupon sans l’utiliser : son offre, s’il peut être utilisé et, sinon, pourquoi (déjà utilisé, expiré ou en pause). |
| POST | /coupons/{code}/redeemUtilise un coupon une seule fois. { amount } (la vente, dans la devise de l’espace de travail) et { branchId } facultatifs. 409 si déjà utilisé ou si l’offre est terminée. Webhook : coupon.redeemed. |
| GET | /coupon-offersListe les offres de coupons avec leur valeur, leur date de fin et si l’on peut créer des codes maintenant (usable). |
| POST | /coupon-offers/{id}/couponsCrée des codes d’une offre. { customerId } donne un code à ce client (il apparaît sur la page de sa carte) ; ou { count: 1–500 } codes non attribués à imprimer ou partager. 201 avec les codes ; 409 si l’offre est en pause ou terminée. |
curl -X POST https://walaora.com/api/v1/cards/ABCDE23456/earn \
-H "Authorization: Bearer wl_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: receipt-10045" \
-d '{"stamps": 1}'Nouvelles tentatives et idempotence
Pour les requêtes POST, envoyez un en-tête Idempotency-Key (toute chaîne unique, par exemple votre numéro de ticket). Une nouvelle tentative avec la même clé dans les 24 heures renvoie la réponse d’origine au lieu d’ajouter deux tampons.
Erreurs
Les erreurs renvoient un corps JSON { error: { code, message } } avec un statut HTTP : 401 clé invalide · 403 plan_required · 404 introuvable · 409 cooldown / no_reward / program_inactive · 422 validation · 429 limite de requêtes atteinte.
Webhooks
Ajoutez un point de terminaison HTTPS dans le tableau de bord et choisissez les événements. Chaque envoi est une requête POST avec le JSON { id, type, createdAt, data } et les en-têtes suivants : Walaora-Event, Walaora-Delivery et Walaora-Signature. Répondez par un code 2xx dans un délai de 10 secondes. Les envois échoués sont retentés après 1 min, 5 min, 30 min, 2 h et 12 h.
Événements
customer.enrolledcard.earnedreward.unlockedreward.redeemedcard.adjustedcustomer.deletedfeedback.receivedpackage.soldpackage.usedperk.usedcoupon.redeemed
Vérifier les signatures (Node.js)
import crypto from "node:crypto";
// rawBody: the exact request body string; header: req.headers["walaora-signature"]
export function verifyWalaora(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // replay window
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}