W
WalaaPassAPI v1
Dashboard

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

  1. Go to API Keys in your dashboard sidebar
  2. Click Generate API Key
  3. Copy the key immediately — it won't be shown again
  4. Store it securely (environment variable, secrets manager, encrypted vault)

Key Format

PropertyValue
Prefixwlp_
Length52 characters total (4 prefix + 48 hex)
Examplewlp_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4
Active keys1 per merchant
HashingSHA-256 (raw key never stored)

Authentication Errors

json
// 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

LimitValue
Requests per minute60
BurstNo burst allowance
ScopePer API key
EnforcementIn-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
HeaderDescription
X-RateLimit-LimitMaximum requests per window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetSeconds until the window resets

When you exceed the rate limit, the API returns 429 Too Many Requests:

json
{ "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)

json
{
  "data": {
    "customer_number": 100042,
    "name": "Ahmed Ali",
    "..."
  }
}

Success (collection with pagination)

json
{
  "data": [ ... ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 342,
    "total_pages": 18
  }
}

Error

json
{
  "error": "Human-readable error message"
}

HTTP Status Codes

CodeMeaningWhen
200SuccessRequest completed successfully
400Bad RequestInvalid parameters, missing required fields, wrong card type
401UnauthorizedMissing, invalid, or expired API key
403ForbiddenInsufficient scope or plan downgraded
404Not FoundCustomer/resource doesn't exist or belongs to another merchant
409ConflictConcurrent update — retry the request
429Too Many RequestsRate limit exceeded or stamp cooldown active
500Server ErrorInternal error — retry with exponential backoff

Scopes

API keys can have one or both scopes:

ScopeHTTP MethodsDescription
readGETView customers, cards, branches, transactions
writePOST, PATCH, DELETEAdd 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.

MethodExample
By Customer IDGET /customers/100042
By phone numberGET /customers?mobile=+966501234567
By searchGET /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
GET /customers

Scope: read

Query Parameters:

ParameterTypeDefaultDescription
pageinteger1Page number (min: 1)
page_sizeinteger20Results per page (1–100)
searchstringSearch by name, mobile number, or email
card_iduuidFilter by loyalty card ID
mobilestringExact match by phone number
customer_numberintegerExact match by Customer ID

Example Request:

bash
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:

bash
curl "https://walaapass.com/api/v1/customers?mobile=%2B966501234567" \
  -H "Authorization: Bearer wlp_your_api_key_here"

Response:

json
{
  "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:

FieldDescription
customer_numberCustomer ID — the unique 6-digit numeric ID shown in your dashboard. Use this for all API calls
current_stampsStamps on current cycle (resets after reward)
lifetime_stampsTotal stamps ever earned
rewards_availableUnredeemed rewards
rewards_redeemedTotal rewards redeemed
current_balanceTotal discount amount given (discount cards)
lifetime_balanceTotal monetary benefit given
pass_statusOne of: registered, card_downloaded, pass_installed, pass_removed, notifications_disabled
membership_started_atWhen subscription was activated (subscription cards)
membership_expires_atWhen subscription expires (subscription cards)

Get Customer

GET
GET /customers/:customer_id

Scope: read

Returns a single customer with their card and branch details included.

Example Request:

bash
curl "https://walaapass.com/api/v1/customers/100042" \
  -H "Authorization: Bearer wlp_your_api_key_here"

Response:

json
{
  "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
PATCH /customers/:customer_id

Scope: write

Allowed Fields:

FieldTypeDescription
namestringCustomer's full name
mobilestringMobile number (with country code)
emailstringEmail address
genderstringmale, female, other, or unknown
date_of_birthstringFormat: YYYY-MM-DD

All fields are optional — only provided fields are updated. Fields not listed above are rejected for security.

Example Request:

bash
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
POST /customers/:customer_id/stamps

Scope: write

FieldTypeDefaultDescription
countinteger1Number of stamps to add (1–10)

Business Logic:

  • Respects the card's stamp cooldown setting — returns 429 if 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: true when a reward was triggered
  • Uses optimistic concurrency — returns 409 on race conditions

Example Request:

bash
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):

json
{
  "data": {
    "customer_number": 100042,
    "current_stamps": 6,
    "lifetime_stamps": 48,
    "rewards_available": 2,
    "..."
  },
  "reward_earned": false
}

Response (reward earned):

json
{
  "data": {
    "customer_number": 100042,
    "current_stamps": 0,
    "lifetime_stamps": 50,
    "rewards_available": 3,
    "..."
  },
  "reward_earned": true
}

Cooldown Error (429):

json
{ "error": "Stamp cooldown: please wait 12 minutes" }

Remove Stamps

DELETE
DELETE /customers/:customer_id/stamps

Scope: write

FieldTypeDefaultDescription
countinteger1Number of stamps to remove (1–10)

Stamps won't go below 0.

Example:

bash
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
POST /customers/:customer_id/rewards/redeem

Scope: write

No request body needed. Redeems exactly one reward.

  • Returns 400 if the customer has no available rewards
  • Returns 409 on concurrent redemption (retry)
  • Sends a push notification to the customer's wallet
  • Creates a REWARD_REDEEMED transaction

Example:

bash
curl -X POST "https://walaapass.com/api/v1/customers/100042/rewards/redeem" \
  -H "Authorization: Bearer wlp_your_api_key_here"

Response:

json
{
  "data": {
    "customer_number": 100042,
    "rewards_available": 1,
    "rewards_redeemed": 6,
    "..."
  }
}

Error (no rewards):

json
{ "error": "No rewards available" }

Discount

Apply Discount

POST
POST /customers/:customer_id/discount

Scope: write

FieldTypeRequiredDescription
amountnumberYesPurchase 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:

bash
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:

json
{
  "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:

json
{ "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
POST /customers/:customer_id/membership

Scope: write

FieldTypeRequiredDescription
actionstringYes"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:

bash
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:

json
{
  "data": {
    "started_at": "2024-03-01T10:00:00.000Z",
    "expires_at": "2024-03-31T10:00:00.000Z",
    "days_remaining": 30
  }
}

Errors:

json
{ "error": "Invalid action. Use 'activate' or 'renew'." }
{ "error": "Card is not a subscription type" }

Branches

List Branches

GET
GET /branches

Scope: read

Returns all active branches. Useful for filtering customers or transactions by branch.

Example:

bash
curl "https://walaapass.com/api/v1/branches" \
  -H "Authorization: Bearer wlp_your_api_key_here"

Response:

json
{
  "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
GET /cards

Scope: read

Returns all active loyalty cards with their reward configurations.

Example:

bash
curl "https://walaapass.com/api/v1/cards" \
  -H "Authorization: Bearer wlp_your_api_key_here"

Response:

json
{
  "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:

Typetype_value meaningmax_discount meaning
stampStamps per reward cycle
rewardNumber of rewards givenExpiry days for rewards
subscriptionDuration in days
discountDiscount percentageMax discount cap (in currency)

Transactions

List Transactions

GET
GET /transactions

Scope: read

Query Parameters:

ParameterTypeDefaultDescription
pageinteger1Page number
page_sizeinteger20Results per page (1–100)
typestringFilter by transaction type
card_iduuidFilter by card
branch_iduuidFilter by branch
customer_numberintegerFilter by Customer ID

Transaction Types:

TypeDescription
REGISTERCustomer registered
CARD_DOWNLOADEDPass added to wallet
STAMP_ADDEDStamp(s) added
STAMP_REMOVEDStamp(s) removed
REWARD_EARNEDReward threshold reached
REWARD_REDEEMEDReward redeemed
DISCOUNT_REDEEMEDDiscount applied
MEMBERSHIP_ACTIVATEDSubscription activated
MEMBERSHIP_RENEWEDSubscription renewed
CUSTOMER_UPDATEDCustomer profile updated
PASS_REMOVEDPass removed from wallet
NOTIFICATION_SENTPush notification sent

Example — get all stamps for a customer:

bash
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:

json
{
  "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

bash
# 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

javascript
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

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
<?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:

  1. Cashier scans customer's wallet pass barcode or enters their phone number
  2. POS looks up customer: GET /customers?mobile=+966...
  3. Based on card type:
    • Stamp card: POST /customers/:customer_id/stamps — wallet updates instantly
    • Discount card: POST /customers/:customer_id/discount with purchase amount — display final total
    • Subscription card: Check membership_expires_at to verify active membership
  4. 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:

  1. Set up a cron job (every 15 minutes or hourly)
  2. Call GET /customers?page=1&page_size=100 and iterate all pages
  3. Upsert into your CRM by mobile number or email
  4. 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 filters
  • GET /customers — demographics and loyalty status
  • GET /cards — card configuration
  • GET /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:

  1. Customer logs into your app with their phone number
  2. Look up: GET /customers?mobile=+966...
  3. Display stamp progress, rewards, membership status
  4. Staff can add stamps: POST /customers/:customer_id/stamps
  5. Customers track history: GET /transactions?customer_number=100042

WhatsApp Bot Integration

Build a chatbot that lets customers check loyalty status via WhatsApp.

Flow:

  1. Customer sends their phone number
  2. Bot calls: GET /customers?mobile=+966...
  3. Bot replies with stamp count, rewards, and membership status
  4. Staff can stamp by command: POST /customers/:customer_id/stamps

Multi-Location Management

Monitor and compare performance across all your branches.

Flow:

  1. Fetch all branches: GET /branches
  2. For each, fetch transactions: GET /transactions?branch_id=...
  3. Calculate: stamps issued, rewards redeemed, new customers
  4. Generate daily/weekly branch comparison reports

Automated Reward Programs

Create conditional rewards based on purchase amount or frequency.

Example:

javascript
// 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

  1. Never expose your API key in client-side code — keep it server-side only
  2. Use environment variables — never hardcode keys in source code
  3. Rotate keys periodically — revoke and regenerate every 90 days
  4. Use minimum required scopes — read-only keys for analytics, read+write for POS
  5. Monitor the dashboard — check API usage stats for unusual patterns
  6. Always use HTTPS — API requests over HTTP will be rejected
  7. Validate responses — always check HTTP status codes before processing data
  8. Implement retry logic — use exponential backoff for 429 and 500 errors
  9. Handle 409 conflicts — retry write operations that return 409

Error Handling

javascript
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