Referensi API Walaora
Hubungkan sistem kasir (POS), toko online, atau sistem internal. JSON melalui HTTPS, satu kunci API per integrasi.
Autentikasi
Buat kunci di Dasbor → API & webhook (paket Bisnis atau Enterprise). Kirimkan kunci tersebut di setiap permintaan. Kunci hanya ditampilkan sekali; cabut kunci jika bocor. Batas: 120 permintaan per menit per kunci.
Authorization: Bearer wl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxEndpoint
Base URL: https://walaora.com/api/v1
| GET | /programsDaftar program loyalitas. |
| GET | /customers?phone=0551234567&tag=vipCari pelanggan (50 per halaman, ?page=2…). ?tag=vip menampilkan pelanggan dengan tag itu; setiap pelanggan menyertakan tag-nya. |
| POST | /customersDaftarkan pelanggan ke sebuah program. Body: { programId, name, phone, email?, marketingConsent?, locale? }. Mengembalikan 201 jika dibuat, 200 jika sudah terdaftar. |
| GET | /cards/{serial}Kartu berdasarkan kode pada kartu / QR pelanggan. |
| POST | /cards/{serial}/earnProgram stempel: { stamps?: 1–10 }. Program poin: { amount } dalam mata uang ruang kerja. Opsional { branchId }. Kartu multi-stempel: kirim setId, salah satu set kartu. |
| POST | /cards/{serial}/redeemTukarkan satu hadiah yang tersedia, atau kirim {"rewardId"} untuk menukarkan hadiah katalog yang dihargai dalam stempel/poin. |
| POST | /cards/{serial}/sellPaket prabayar: menjual satu paket, pemakaiannya ditambahkan ke kartu (pembayaran di kasir Anda). Webhook: package.sold. |
| POST | /cards/{serial}/usePaket prabayar: mengurangi satu pemakaian (ditolak jika habis atau kedaluwarsa). Kartu karyawan: mencatat keuntungan dalam batas hariannya. Webhook: package.used, perk.used. |
| GET | /coupons/{code}Memeriksa kupon tanpa memakainya: penawarannya, apakah bisa dipakai dan, jika tidak, alasannya (sudah ditukarkan, kedaluwarsa, atau dijeda). |
| POST | /coupons/{code}/redeemMemakai kupon satu kali. { amount } (nilai penjualan, dalam mata uang ruang kerja) dan { branchId } opsional. 409 jika sudah dipakai atau penawaran sudah berakhir. Webhook: coupon.redeemed. |
| GET | /coupon-offersDaftar penawaran kupon beserta nilainya, tanggal berakhir, dan apakah kode bisa dibuat sekarang (usable). |
| POST | /coupon-offers/{id}/couponsBuat kode dari sebuah penawaran. { customerId } memberi satu kode kepada pelanggan itu (tampil di halaman kartunya); atau { count: 1–500 } kode tanpa pelanggan untuk dicetak atau dibagikan. 201 beserta kodenya; 409 jika penawaran dijeda atau sudah berakhir. |
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}'Percobaan ulang & idempotensi
Untuk permintaan POST, kirim header Idempotency-Key (string unik apa pun, misalnya nomor struk Anda). Mencoba ulang dengan kunci yang sama dalam 24 jam akan mengembalikan respons asli, bukan menambahkan stempel dua kali.
Galat
Galat mengembalikan body JSON { error: { code, message } } dengan status HTTP: 401 kunci salah · 403 plan_required · 404 tidak ditemukan · 409 cooldown / no_reward / program_inactive · 422 validasi · 429 batas permintaan terlampaui.
Webhooks
Tambahkan endpoint HTTPS di dasbor dan pilih event. Setiap pengiriman berupa POST dengan JSON { id, type, createdAt, data } dan header berikut: Walaora-Event, Walaora-Delivery, dan Walaora-Signature. Balas dengan 2xx dalam 10 detik. Pengiriman yang gagal dicoba ulang setelah 1 menit, 5 menit, 30 menit, 2 jam, dan 12 jam.
Event
customer.enrolledcard.earnedreward.unlockedreward.redeemedcard.adjustedcustomer.deletedfeedback.receivedpackage.soldpackage.usedperk.usedcoupon.redeemed
Memverifikasi tanda tangan (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 ?? ""));
}