Walaora API reference
Connect a POS, e-commerce store or internal system. JSON over HTTPS, one API key per integration.
Authentication
Create a key in Dashboard → API & webhooks (Business or Enterprise plan). Send it on every request. Keys are shown once; revoke them if exposed. Limit: 120 requests per minute per key.
Authorization: Bearer wl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxEndpoints
Base URL: https://walaora.com/api/v1
| GET | /programsList loyalty programs. |
| GET | /customers?phone=0551234567&tag=vipFind customers (50 per page, ?page=2…). ?tag=vip lists customers with that tag; each customer includes its tags. |
| POST | /customersEnroll a customer in a program. Body: { programId, name, phone, email?, marketingConsent?, locale? }. Returns 201 when created, 200 if already enrolled. |
| GET | /cards/{serial}Card by the code on the customer’s card / QR. |
| POST | /cards/{serial}/earnStamp programs: { stamps?: 1–10 }. Points programs: { amount } in the workspace currency. Optional { branchId }. Multi-stamp cards: send setId, one of the card’s sets. |
| POST | /cards/{serial}/redeemRedeem one available reward, or send {"rewardId"} to redeem a catalogue reward priced in stamps/points. |
| POST | /cards/{serial}/sellPrepaid packages: sell one package; its uses are added to the card (payment is taken at your till). Webhook: package.sold. |
| POST | /cards/{serial}/usePrepaid packages: take one use (refused when empty or expired). Employee cards: record the perk within its daily limit. Webhooks: package.used, perk.used. |
| GET | /coupons/{code}Check a coupon without using it: its offer, whether it can be used and, if not, why (redeemed, expired or paused). |
| POST | /coupons/{code}/redeemUse a coupon once. Optional { amount } (the sale, in the workspace currency) and { branchId }. 409 when already used or the offer has ended. Webhook: coupon.redeemed. |
| GET | /coupon-offersList coupon offers with their value, end date and whether codes can be created now (usable). |
| POST | /coupon-offers/{id}/couponsCreate codes of an offer. { customerId } gives one code to that customer (it shows on their card page); or { count: 1–500 } unassigned codes to print or share. 201 with the codes; 409 when the offer is paused or has ended. |
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}'Retries & idempotency
For POST requests, send an Idempotency-Key header (any unique string, e.g. your receipt number). Retrying with the same key within 24 hours returns the original response instead of stamping twice.
Errors
Errors return a JSON body { error: { code, message } } with an HTTP status: 401 bad key · 403 plan_required · 404 not found · 409 cooldown / no_reward / program_inactive · 422 validation · 429 rate limited.
Webhooks
Add an HTTPS endpoint in the dashboard and choose events. Each delivery is a POST with JSON { id, type, createdAt, data } and these headers: Walaora-Event, Walaora-Delivery and Walaora-Signature. Respond with 2xx within 10 seconds. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 12 h.
Events
customer.enrolledcard.earnedreward.unlockedreward.redeemedcard.adjustedcustomer.deletedfeedback.receivedpackage.soldpackage.usedperk.usedcoupon.redeemed
Verifying 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 ?? ""));
}