WalaaPass Merchant API v1
Programmatic access to your entire loyalty program — customers, stamps, rewards, discounts, memberships, branches, cards, and transactions.
Overview
The WalaaPass Merchant API is a RESTful JSON API that lets you integrate your loyalty program with any system. Build powerful integrations that automate your workflow, sync data across platforms, and create custom experiences for your customers.
Base URL:
https://walaapass.com/api/v1
What You Can Do
- Automate stamping from your POS system — no manual entry needed
- Sync customer data with your CRM, ERP, or marketing platform
- Build custom dashboards with real-time loyalty analytics
- Power mobile apps with customer lookup and stamp management
- Trigger rewards automatically based on purchase amount or frequency
- Apply discounts at checkout via your own payment terminal
- Manage memberships — activate, renew, and track subscription status
- Export and analyze transaction history for business intelligence
Authentication
All API requests require a Bearer token in the Authorization header.
Authorization: Bearer wlp_your_api_key_here
Getting Your API Key
- Go to API Keys in your dashboard sidebar
- Click Generate API Key
- Copy the key immediately — it won't be shown again
- Store it securely (environment variable, secrets manager, encrypted vault)
Key Format
| Property | Value |
|---|---|
| Prefix | wlp_ |
| Length | 52 characters total (4 prefix + 48 hex) |
| Example | wlp_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4 |
| Active keys | 1 per merchant |
| Hashing | SHA-256 (raw key never stored) |
Authentication Errors
// Missing or malformed header → 401
{ "error": "Missing or invalid Authorization header. Use: Bearer wlp_..." }
// Invalid key format → 401
{ "error": "Invalid API key format" }
// Key not found or revoked → 401
{ "error": "Invalid API key" }
// Key expired → 401
{ "error": "API key has expired" }
// Plan downgraded → 403
{ "error": "API access requires the Special plan" }
Rate Limits
| Limit | Value |
|---|---|
| Requests per minute | 60 |
| Burst | No burst allowance |
| Scope | Per API key |
| Enforcement | In-memory sliding window |
Rate Limit Headers
Every API response includes rate limit headers:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 60
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per window |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Seconds until the window resets |
When you exceed the rate limit, the API returns 429 Too Many Requests:
{ "error": "Rate limit exceeded. Maximum 60 requests per minute." }
Additional header on 429 responses:
Retry-After: 60
Tip: Monitor X-RateLimit-Remaining in your integration to proactively throttle before hitting the limit.
Response Format
Success (single resource)
{
"data": {
"customer_number": 100042,
"name": "Ahmed Ali",
"..."
}
}
Success (collection with pagination)
{
"data": [ ... ],
"pagination": {
"page": 1,
"page_size": 20,
"total": 342,
"total_pages": 18
}
}
Error
{
"error": "Human-readable error message"
}
HTTP Status Codes
| Code | Meaning | When |
|---|---|---|
200 | Success | Request completed successfully |
400 | Bad Request | Invalid parameters, missing required fields, wrong card type |
401 | Unauthorized | Missing, invalid, or expired API key |
403 | Forbidden | Insufficient scope or plan downgraded |
404 | Not Found | Customer/resource doesn't exist or belongs to another merchant |
409 | Conflict | Concurrent update — retry the request |
429 | Too Many Requests | Rate limit exceeded or stamp cooldown active |
500 | Server Error | Internal error — retry with exponential backoff |
Scopes
API keys can have one or both scopes:
| Scope | HTTP Methods | Description |
|---|---|---|
read | GET | View customers, cards, branches, transactions |
write | POST, PATCH, DELETE | Add stamps, redeem rewards, update customers, apply discounts |
Best practice: If your integration only reads data (e.g., analytics dashboard, CRM sync), create a read-only key to minimize risk.
Customer Identification
All customer endpoints use the Customer ID — the same 6-digit numeric ID shown in your dashboard. No internal UUIDs are exposed in the API.
| Method | Example |
|---|---|
| By Customer ID | GET /customers/100042 |
| By phone number | GET /customers?mobile=+966501234567 |
| By search | GET /customers?search=ahmed |
Tip: Use the Customer ID for direct lookups and mobile for phone-based integrations like POS barcode scanning.
Endpoints Reference
Customers
List Customers
GET /customers
Scope: read
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number (min: 1) |
page_size | integer | 20 | Results per page (1–100) |
search | string | — | Search by name, mobile number, or email |
card_id | uuid | — | Filter by loyalty card ID |
mobile | string | — | Exact match by phone number |
customer_number | integer | — | Exact match by Customer ID |
Example Request:
curl "https://walaapass.com/api/v1/customers?page=1&page_size=10&search=ahmed" \
-H "Authorization: Bearer wlp_your_api_key_here"
Example — lookup by phone:
curl "https://walaapass.com/api/v1/customers?mobile=%2B966501234567" \
-H "Authorization: Bearer wlp_your_api_key_here"
Response:
{
"data": [
{
"customer_number": 100042,
"name": "Ahmed Ali",
"mobile": "+966501234567",
"email": "ahmed@example.com",
"gender": "male",
"date_of_birth": "1990-05-15",
"current_stamps": 5,
"lifetime_stamps": 47,
"rewards_available": 2,
"rewards_redeemed": 5,
"current_balance": 0,
"lifetime_balance": 0,
"membership_started_at": null,
"membership_expires_at": null,
"pass_status": "pass_installed",
"card_id": "card-uuid-here",
"branch_id": "branch-uuid-here",
"created_at": "2024-01-15T10:30:00.000Z",
"updated_at": "2024-03-01T14:20:00.000Z"
}
],
"pagination": {
"page": 1,
"page_size": 10,
"total": 342,
"total_pages": 35
}
}
Customer Fields Explained:
| Field | Description |
|---|---|
customer_number | Customer ID — the unique 6-digit numeric ID shown in your dashboard. Use this for all API calls |
current_stamps | Stamps on current cycle (resets after reward) |
lifetime_stamps | Total stamps ever earned |
rewards_available | Unredeemed rewards |
rewards_redeemed | Total rewards redeemed |
current_balance | Total discount amount given (discount cards) |
lifetime_balance | Total monetary benefit given |
pass_status | One of: registered, card_downloaded, pass_installed, pass_removed, notifications_disabled |
membership_started_at | When subscription was activated (subscription cards) |
membership_expires_at | When subscription expires (subscription cards) |
Get Customer
GET /customers/:customer_id
Scope: read
Returns a single customer with their card and branch details included.
Example Request:
curl "https://walaapass.com/api/v1/customers/100042" \
-H "Authorization: Bearer wlp_your_api_key_here"
Response:
{
"data": {
"customer_number": 100042,
"name": "Ahmed Ali",
"mobile": "+966501234567",
"email": "ahmed@example.com",
"current_stamps": 5,
"rewards_available": 2,
"card_id": "card-uuid",
"branch_id": "branch-uuid",
"cards": {
"id": "card-uuid",
"name": "Gold Loyalty",
"display_name": "Gold Card",
"type": "stamp"
},
"branches": {
"id": "branch-uuid",
"name": "Downtown Branch"
},
"..."
}
}
Update Customer
PATCH /customers/:customer_id
Scope: write
Allowed Fields:
| Field | Type | Description |
|---|---|---|
name | string | Customer's full name |
mobile | string | Mobile number (with country code) |
email | string | Email address |
gender | string | male, female, other, or unknown |
date_of_birth | string | Format: YYYY-MM-DD |
All fields are optional — only provided fields are updated. Fields not listed above are rejected for security.
Example Request:
curl -X PATCH "https://walaapass.com/api/v1/customers/100042" \
-H "Authorization: Bearer wlp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Ahmed Mohammed Ali",
"email": "ahmed.new@example.com"
}'
Stamps
Add Stamps
POST /customers/:customer_id/stamps
Scope: write
| Field | Type | Default | Description |
|---|---|---|---|
count | integer | 1 | Number of stamps to add (1–10) |
Business Logic:
- Respects the card's stamp cooldown setting — returns
429if too soon - Automatically awards a reward when stamps reach the threshold (resets stamp count to 0)
- If multiple rewards are earned at once (e.g., adding 10 stamps to a 4-stamp threshold), all are awarded
- Sends a push notification to the customer's Apple/Google Wallet pass
- Returns
reward_earned: truewhen a reward was triggered - Uses optimistic concurrency — returns
409on race conditions
Example Request:
curl -X POST "https://walaapass.com/api/v1/customers/100042/stamps" \
-H "Authorization: Bearer wlp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"count": 1}'
Response (no reward):
{
"data": {
"customer_number": 100042,
"current_stamps": 6,
"lifetime_stamps": 48,
"rewards_available": 2,
"..."
},
"reward_earned": false
}
Response (reward earned):
{
"data": {
"customer_number": 100042,
"current_stamps": 0,
"lifetime_stamps": 50,
"rewards_available": 3,
"..."
},
"reward_earned": true
}
Cooldown Error (429):
{ "error": "Stamp cooldown: please wait 12 minutes" }
Remove Stamps
DELETE /customers/:customer_id/stamps
Scope: write
| Field | Type | Default | Description |
|---|---|---|---|
count | integer | 1 | Number of stamps to remove (1–10) |
Stamps won't go below 0.
Example:
curl -X DELETE "https://walaapass.com/api/v1/customers/100042/stamps" \
-H "Authorization: Bearer wlp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"count": 1}'
Rewards
Redeem Reward
POST /customers/:customer_id/rewards/redeem
Scope: write
No request body needed. Redeems exactly one reward.
- Returns
400if the customer has no available rewards - Returns
409on concurrent redemption (retry) - Sends a push notification to the customer's wallet
- Creates a
REWARD_REDEEMEDtransaction
Example:
curl -X POST "https://walaapass.com/api/v1/customers/100042/rewards/redeem" \
-H "Authorization: Bearer wlp_your_api_key_here"
Response:
{
"data": {
"customer_number": 100042,
"rewards_available": 1,
"rewards_redeemed": 6,
"..."
}
}
Error (no rewards):
{ "error": "No rewards available" }
Discount
Apply Discount
POST /customers/:customer_id/discount
Scope: write
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Purchase total to apply discount to (must be > 0) |
Business Logic:
- Discount rate comes from the customer's discount card (
type_value= percentage) - Respects max discount cap if configured on the card
- Tracks the discount in
current_balance(total discounts given) - Sends a push notification to the customer's wallet
Example:
curl -X POST "https://walaapass.com/api/v1/customers/100042/discount" \
-H "Authorization: Bearer wlp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"amount": 200.00}'
Response:
{
"data": {
"discount": 30.00,
"final_total": 170.00,
"rate": 15
}
}
Example: Card has 15% discount with max cap of 50 SAR. A 500 SAR purchase would give 50 SAR discount (capped), not 75 SAR.
Errors:
{ "error": "Invalid amount" } // amount <= 0 or not a number
{ "error": "Card is not a discount type" } // customer's card isn't a discount card
{ "error": "Invalid JSON body" } // malformed request body
Membership / Subscription
Activate or Renew Membership
POST /customers/:customer_id/membership
Scope: write
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | "activate" or "renew" |
Business Logic:
- Duration comes from the subscription card's
type_value(in days, default 30) - Activate: Sets start date to now, expiry to now + duration
- Renew: Extends from current expiry date (or from now if already expired)
- Sends a push notification with remaining days
Example:
curl -X POST "https://walaapass.com/api/v1/customers/100042/membership" \
-H "Authorization: Bearer wlp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"action": "activate"}'
Response:
{
"data": {
"started_at": "2024-03-01T10:00:00.000Z",
"expires_at": "2024-03-31T10:00:00.000Z",
"days_remaining": 30
}
}
Errors:
{ "error": "Invalid action. Use 'activate' or 'renew'." }
{ "error": "Card is not a subscription type" }
Branches
List Branches
GET /branches
Scope: read
Returns all active branches. Useful for filtering customers or transactions by branch.
Example:
curl "https://walaapass.com/api/v1/branches" \
-H "Authorization: Bearer wlp_your_api_key_here"
Response:
{
"data": [
{
"id": "branch-uuid",
"name": "Downtown Branch",
"mobile": "+966501234567",
"description": "Main branch in city center",
"google_map_link": "https://maps.google.com/...",
"latitude": 24.7136,
"longitude": 46.6753,
"is_active": true,
"created_at": "2024-01-01T00:00:00.000Z",
"updated_at": "2024-02-15T12:00:00.000Z"
}
]
}
Cards
List Cards
GET /cards
Scope: read
Returns all active loyalty cards with their reward configurations.
Example:
curl "https://walaapass.com/api/v1/cards" \
-H "Authorization: Bearer wlp_your_api_key_here"
Response:
{
"data": [
{
"id": "card-uuid",
"name": "Gold Loyalty",
"display_name": "Gold Card",
"display_name_ar": "البطاقة الذهبية",
"type": "stamp",
"type_value": 10,
"max_discount": null,
"card_color": "#1a1a2e",
"text_color": "#ffffff",
"is_active": true,
"branch_id": null,
"stamp_cooldown_minutes": 30,
"created_at": "2024-01-01T00:00:00.000Z",
"updated_at": "2024-02-15T12:00:00.000Z",
"card_rewards": [
{
"id": "reward-uuid",
"name": "Free Coffee",
"name_ar": "قهوة مجانية",
"stamps_required": 10,
"is_active": true
}
]
}
]
}
Card Types Explained:
| Type | type_value meaning | max_discount meaning |
|---|---|---|
stamp | Stamps per reward cycle | — |
reward | Number of rewards given | Expiry days for rewards |
subscription | Duration in days | — |
discount | Discount percentage | Max discount cap (in currency) |
Transactions
List Transactions
GET /transactions
Scope: read
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
page_size | integer | 20 | Results per page (1–100) |
type | string | — | Filter by transaction type |
card_id | uuid | — | Filter by card |
branch_id | uuid | — | Filter by branch |
customer_number | integer | — | Filter by Customer ID |
Transaction Types:
| Type | Description |
|---|---|
REGISTER | Customer registered |
CARD_DOWNLOADED | Pass added to wallet |
STAMP_ADDED | Stamp(s) added |
STAMP_REMOVED | Stamp(s) removed |
REWARD_EARNED | Reward threshold reached |
REWARD_REDEEMED | Reward redeemed |
DISCOUNT_REDEEMED | Discount applied |
MEMBERSHIP_ACTIVATED | Subscription activated |
MEMBERSHIP_RENEWED | Subscription renewed |
CUSTOMER_UPDATED | Customer profile updated |
PASS_REMOVED | Pass removed from wallet |
NOTIFICATION_SENT | Push notification sent |
Example — get all stamps for a customer:
curl "https://walaapass.com/api/v1/transactions?customer_number=100042&type=STAMP_ADDED&page_size=50" \
-H "Authorization: Bearer wlp_your_api_key_here"
Response:
{
"data": [
{
"id": "txn-uuid",
"type": "STAMP_ADDED",
"details": "1 stamp added via API",
"metadata": {
"count": 1,
"previousStamps": 4,
"newStamps": 5,
"source": "api"
},
"card_id": "card-uuid",
"branch_id": "branch-uuid",
"created_at": "2024-03-01T14:30:00.000Z",
"customers": { "customer_number": 100042, "name": "Ahmed", "mobile": "+966501234567" },
"cards": { "id": "card-uuid", "name": "Gold Card" },
"branches": { "id": "branch-uuid", "name": "Downtown" }
}
],
"pagination": { "page": 1, "page_size": 50, "total": 230, "total_pages": 5 }
}
Code Examples
cURL
# List customers with search
curl "https://walaapass.com/api/v1/customers?search=ahmed&page_size=10" \
-H "Authorization: Bearer wlp_your_api_key_here"
# Look up customer by phone number
curl "https://walaapass.com/api/v1/customers?mobile=%2B966501234567" \
-H "Authorization: Bearer wlp_your_api_key_here"
# Look up customer by Customer ID
curl "https://walaapass.com/api/v1/customers?customer_number=100042" \
-H "Authorization: Bearer wlp_your_api_key_here"
# Get a single customer
curl "https://walaapass.com/api/v1/customers/100042" \
-H "Authorization: Bearer wlp_your_api_key_here"
# Add 2 stamps
curl -X POST "https://walaapass.com/api/v1/customers/100042/stamps" \
-H "Authorization: Bearer wlp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"count": 2}'
# Redeem a reward
curl -X POST "https://walaapass.com/api/v1/customers/100042/rewards/redeem" \
-H "Authorization: Bearer wlp_your_api_key_here"
# Apply discount on 150 SAR purchase
curl -X POST "https://walaapass.com/api/v1/customers/100042/discount" \
-H "Authorization: Bearer wlp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"amount": 150}'
# Activate membership
curl -X POST "https://walaapass.com/api/v1/customers/100042/membership" \
-H "Authorization: Bearer wlp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"action": "activate"}'
# Update customer info
curl -X PATCH "https://walaapass.com/api/v1/customers/100042" \
-H "Authorization: Bearer wlp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"name": "Ahmed Ali", "email": "ahmed@company.com"}'
# List transactions for a customer
curl "https://walaapass.com/api/v1/transactions?customer_number=100042&page_size=50" \
-H "Authorization: Bearer wlp_your_api_key_here"
JavaScript / TypeScript
const API_KEY = process.env.WALAAPASS_API_KEY;
const BASE_URL = 'https://walaapass.com/api/v1';
async function walaaApi(path, options = {}) {
const res = await fetch(`${BASE_URL}${path}`, {
...options,
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
...options.headers,
},
});
// Log rate limit status
const remaining = res.headers.get('X-RateLimit-Remaining');
if (remaining && parseInt(remaining) < 10) {
console.warn(`API rate limit warning: ${remaining} requests remaining`);
}
const json = await res.json();
if (!res.ok) throw new Error(json.error || `HTTP ${res.status}`);
return json;
}
// List all customers (paginated)
const { data: customers, pagination } = await walaaApi('/customers?page=1&page_size=50');
console.log(`Found ${pagination.total} customers`);
// Look up customer by phone number
const { data: results } = await walaaApi('/customers?mobile=%2B966501234567');
const customer = results[0];
// Or look up by Customer ID
const { data: singleCustomer } = await walaaApi('/customers/100042');
// Add a stamp after purchase
const { data: updated, reward_earned } = await walaaApi(`/customers/${customer.customer_number}/stamps`, {
method: 'POST',
body: JSON.stringify({ count: 1 }),
});
if (reward_earned) {
console.log('Customer earned a reward!');
}
// Apply discount at checkout
const { data: discount } = await walaaApi(`/customers/${customer.customer_number}/discount`, {
method: 'POST',
body: JSON.stringify({ amount: 200.00 }),
});
console.log(`Discount: ${discount.discount} SAR, Final: ${discount.final_total} SAR`);
// Redeem reward
await walaaApi(`/customers/${customer.customer_number}/rewards/redeem`, { method: 'POST' });
// Activate subscription
const { data: membership } = await walaaApi(`/customers/${customer.customer_number}/membership`, {
method: 'POST',
body: JSON.stringify({ action: 'activate' }),
});
console.log(`Membership valid for ${membership.days_remaining} days`);
// Get all cards and branches
const { data: cards } = await walaaApi('/cards');
const { data: branches } = await walaaApi('/branches');
Python
import requests
import os
API_KEY = os.environ["WALAAPASS_API_KEY"]
BASE_URL = "https://walaapass.com/api/v1"
session = requests.Session()
session.headers.update({"Authorization": f"Bearer {API_KEY}"})
# List customers
resp = session.get(f"{BASE_URL}/customers", params={"page_size": 50})
data = resp.json()
print(f"Total customers: {data['pagination']['total']}")
print(f"Rate limit remaining: {resp.headers.get('X-RateLimit-Remaining')}")
# Search by phone
resp = session.get(f"{BASE_URL}/customers", params={"mobile": "+966501234567"})
customer = resp.json()["data"][0]
# Add stamp (use Customer ID)
resp = session.post(
f"{BASE_URL}/customers/{customer['customer_number']}/stamps",
json={"count": 1}
)
result = resp.json()
if result.get("reward_earned"):
print("Reward earned!")
# Apply discount
resp = session.post(
f"{BASE_URL}/customers/{customer['customer_number']}/discount",
json={"amount": 150.00}
)
discount = resp.json()["data"]
print(f"Discount: {discount['discount']} SAR | Final: {discount['final_total']} SAR")
# Redeem reward
session.post(f"{BASE_URL}/customers/{customer['customer_number']}/rewards/redeem")
# Activate membership
resp = session.post(
f"{BASE_URL}/customers/{customer['customer_number']}/membership",
json={"action": "activate"}
)
membership = resp.json()["data"]
print(f"Membership: {membership['days_remaining']} days remaining")
# Export all transactions
all_txns = []
page = 1
while True:
resp = session.get(f"{BASE_URL}/transactions", params={"page": page, "page_size": 100})
data = resp.json()
all_txns.extend(data["data"])
if page >= data["pagination"]["total_pages"]:
break
page += 1
print(f"Exported {len(all_txns)} transactions")
PHP
<?php
$apiKey = getenv('WALAAPASS_API_KEY');
$baseUrl = 'https://walaapass.com/api/v1';
function walaaApi($path, $method = 'GET', $body = null) {
global $apiKey, $baseUrl;
$ch = curl_init($baseUrl . $path);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $apiKey",
"Content-Type: application/json",
]);
if ($method !== 'GET') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if ($body) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// Search customer by phone
$result = walaaApi('/customers?mobile=' . urlencode('+966501234567'));
$customer = $result['data'][0];
// Add stamp (use Customer ID)
$stampResult = walaaApi("/customers/{$customer['customer_number']}/stamps", 'POST', ['count' => 1]);
if ($stampResult['reward_earned']) {
echo "Reward earned!\n";
}
// Apply discount
$discount = walaaApi("/customers/{$customer['customer_number']}/discount", 'POST', ['amount' => 100]);
echo "Final total: {$discount['data']['final_total']} SAR\n";
Integration Scenarios
POS System Integration
Automatically stamp customers and apply discounts when they pay at your register.
Flow:
- Cashier scans customer's wallet pass barcode or enters their phone number
- POS looks up customer:
GET /customers?mobile=+966... - Based on card type:
- Stamp card:
POST /customers/:customer_id/stamps— wallet updates instantly - Discount card:
POST /customers/:customer_id/discountwith purchase amount — display final total - Subscription card: Check
membership_expires_atto verify active membership
- Stamp card:
- If reward earned, prompt cashier:
POST /customers/:customer_id/rewards/redeem
CRM / Marketing Platform Sync
Keep your customer database in sync with HubSpot, Salesforce, Zoho, or any CRM.
Flow:
- Set up a cron job (every 15 minutes or hourly)
- Call
GET /customers?page=1&page_size=100and iterate all pages - Upsert into your CRM by mobile number or email
- Sync loyalty data: stamps, rewards, membership status, last visit date
Custom Analytics Dashboard
Build Power BI, Tableau, or custom dashboards with your loyalty data.
Data Sources:
GET /transactions— full activity history with filtersGET /customers— demographics and loyalty statusGET /cards— card configurationGET /branches— branch performance comparisons
Metrics you can build:
- Stamps per day/week/month by branch
- Reward redemption rate
- Customer retention (repeat visits)
- Revenue impact of discounts
- Membership renewal rate
- Top customers by lifetime stamps
Mobile App Backend
Power your own branded mobile app with loyalty features.
Flow:
- Customer logs into your app with their phone number
- Look up:
GET /customers?mobile=+966... - Display stamp progress, rewards, membership status
- Staff can add stamps:
POST /customers/:customer_id/stamps - Customers track history:
GET /transactions?customer_number=100042
WhatsApp Bot Integration
Build a chatbot that lets customers check loyalty status via WhatsApp.
Flow:
- Customer sends their phone number
- Bot calls:
GET /customers?mobile=+966... - Bot replies with stamp count, rewards, and membership status
- Staff can stamp by command:
POST /customers/:customer_id/stamps
Multi-Location Management
Monitor and compare performance across all your branches.
Flow:
- Fetch all branches:
GET /branches - For each, fetch transactions:
GET /transactions?branch_id=... - Calculate: stamps issued, rewards redeemed, new customers
- Generate daily/weekly branch comparison reports
Automated Reward Programs
Create conditional rewards based on purchase amount or frequency.
Example:
// After every purchase, give bonus stamps based on amount
const purchaseAmount = 150; // SAR
const bonusStamps = Math.floor(purchaseAmount / 50); // 1 stamp per 50 SAR
if (bonusStamps > 0) {
await walaaApi(`/customers/${customer.customer_number}/stamps`, {
method: 'POST',
body: JSON.stringify({ count: Math.min(bonusStamps, 10) }),
});
}
Security Best Practices
- Never expose your API key in client-side code — keep it server-side only
- Use environment variables — never hardcode keys in source code
- Rotate keys periodically — revoke and regenerate every 90 days
- Use minimum required scopes — read-only keys for analytics, read+write for POS
- Monitor the dashboard — check API usage stats for unusual patterns
- Always use HTTPS — API requests over HTTP will be rejected
- Validate responses — always check HTTP status codes before processing data
- Implement retry logic — use exponential backoff for 429 and 500 errors
- Handle 409 conflicts — retry write operations that return 409
Error Handling
Recommended Pattern
async function safeApiCall(path, options = {}, retries = 3) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const res = await fetch(`${BASE_URL}${path}`, {
...options,
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
...options.headers
},
});
// Rate limited — wait and retry
if (res.status === 429) {
const wait = parseInt(res.headers.get('Retry-After') || '60');
console.log(`Rate limited. Waiting ${wait}s (attempt ${attempt})`);
await new Promise(r => setTimeout(r, wait * 1000));
continue;
}
// Concurrent conflict — retry immediately
if (res.status === 409 && attempt < retries) {
console.log(`Conflict, retrying (attempt ${attempt})`);
continue;
}
const json = await res.json();
if (!res.ok) {
console.error(`API Error ${res.status}: ${json.error}`);
return null;
}
return json;
} catch (err) {
if (attempt === retries) {
console.error('Network error:', err.message);
return null;
}
await new Promise(r => setTimeout(r, 1000 * attempt));
}
}
return null;
}
Support
For API questions, integration help, or to report issues:
Email: support@walaapass.com