Aller au contenu
walaoraWalaora

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.

N’envoyez les données des clients qu’à leur connaissance, et ne mettez marketingConsent à true que si le client a donné son accord explicite.

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_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Points de terminaison

Base URL: https://walaora.com/api/v1

GET/programs

Lister les programmes de fidélité.

GET/customers?phone=0551234567&tag=vip

Rechercher des clients (50 par page, ?page=2…). ?tag=vip liste les clients ayant cette étiquette ; chaque client inclut ses étiquettes.

POST/customers

Inscrire 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}/earn

Programmes à 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}/sell

Forfaits prépayés : vend un forfait dont les utilisations s’ajoutent à la carte (paiement à votre caisse). Webhook : package.sold.

POST/cards/{serial}/use

Forfaits 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}/redeem

Utilise 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-offers

Liste 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}/coupons

Cré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

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 ?? ""));
}