Blog/Use Cases

How to Build a WhatsApp Loyalty Program via API to Boost Repeat Sales

Build a WhatsApp loyalty program using Rapiwa API. Track customer points, send personalized reward messages, and automate tier upgrades via WhatsApp. Full Python and n8n implementation guide.

by Maya
How to Build a WhatsApp Loyalty Program via API to Boost Repeat Sales

You can build a WhatsApp loyalty program using Rapiwa API to track points, send personalized reward messages, and notify customers of tier upgrades automatically. Customers earn points on purchases, receive WhatsApp updates when their balance changes, and get exclusive offers based on their tier. Rapiwa costs $5/month flat with no per-message fees — making even a 1,000-member loyalty program affordable.

What Makes WhatsApp Loyalty Programs Work

Traditional loyalty cards are forgotten in wallets. Email loyalty updates have 20% open rates. WhatsApp changes this:

  • 98% open rate — customers see their points balance update instantly
  • Conversational — customers reply to check their balance, claim rewards
  • Always accessible — no app to download, no card to carry
  • Instant redemption — send discount codes directly via WhatsApp when rewards are earned

Loyalty Program Architecture

Customer makes purchase
  → Points calculated
  → Database updated
  → Rapiwa API sends WhatsApp points update
  → If tier upgrade: send tier congratulations
  → If redeemable threshold reached: send reward

Step 1: Database Schema

-- Loyalty members table
CREATE TABLE loyalty_members (
    id SERIAL PRIMARY KEY,
    customer_id VARCHAR(50) UNIQUE NOT NULL,
    phone VARCHAR(20) NOT NULL,
    name VARCHAR(100) NOT NULL,
    total_points INT DEFAULT 0,
    tier VARCHAR(20) DEFAULT 'Bronze',  -- Bronze, Silver, Gold, Platinum
    total_spend DECIMAL(10,2) DEFAULT 0,
    joined_at TIMESTAMP DEFAULT NOW(),
    last_activity TIMESTAMP DEFAULT NOW(),
    whatsapp_opted_in BOOLEAN DEFAULT TRUE
);

-- Points transactions log
CREATE TABLE loyalty_transactions (
    id SERIAL PRIMARY KEY,
    customer_id VARCHAR(50) NOT NULL,
    order_id VARCHAR(50),
    points_earned INT DEFAULT 0,
    points_redeemed INT DEFAULT 0,
    balance_after INT NOT NULL,
    transaction_type VARCHAR(30),  -- 'purchase', 'redemption', 'bonus', 'expiry'
    description TEXT,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Create indexes
CREATE INDEX idx_loyalty_phone ON loyalty_members(phone);
CREATE INDEX idx_loyalty_tier ON loyalty_members(tier);

Step 2: Points Calculation and Award

import requests
import psycopg2
from decimal import Decimal
from datetime import datetime

RAPIWA_API_KEY = 'YOUR_API_KEY'

# Loyalty tier thresholds
TIERS = {
    'Bronze': {'min_points': 0, 'earn_rate': 1},       # 1 point per $1
    'Silver': {'min_points': 500, 'earn_rate': 1.5},   # 1.5 points per $1
    'Gold': {'min_points': 2000, 'earn_rate': 2},      # 2 points per $1
    'Platinum': {'min_points': 5000, 'earn_rate': 3},  # 3 points per $1
}

def get_tier(total_points: int) -> str:
    """Determine loyalty tier based on total points."""
    if total_points >= 5000:
        return 'Platinum'
    elif total_points >= 2000:
        return 'Gold'
    elif total_points >= 500:
        return 'Silver'
    return 'Bronze'

def award_points(customer_id: str, order_amount: Decimal, order_id: str, db) -> dict:
    """
    Award loyalty points for a purchase.
    Returns updated member data and whether a tier upgrade occurred.
    """
    # Get current member data
    member = db.fetchone(
        "SELECT * FROM loyalty_members WHERE customer_id = %s",
        [customer_id]
    )
    
    if not member:
        return {'error': 'Member not found'}
    
    earn_rate = TIERS[member['tier']]['earn_rate']
    points_earned = int(float(order_amount) * earn_rate)
    new_total = member['total_points'] + points_earned
    old_tier = member['tier']
    new_tier = get_tier(new_total)
    
    # Update database
    db.execute("""
        UPDATE loyalty_members 
        SET total_points = %s, tier = %s, total_spend = total_spend + %s,
            last_activity = NOW()
        WHERE customer_id = %s
    """, [new_total, new_tier, float(order_amount), customer_id])
    
    # Log transaction
    db.execute("""
        INSERT INTO loyalty_transactions 
        (customer_id, order_id, points_earned, balance_after, transaction_type, description)
        VALUES (%s, %s, %s, %s, 'purchase', %s)
    """, [customer_id, order_id, points_earned, new_total, f"Purchase ${order_amount}"])
    
    # Send WhatsApp notification
    tier_upgraded = new_tier != old_tier
    send_points_notification(
        member=member,
        points_earned=points_earned,
        new_total=new_total,
        new_tier=new_tier,
        tier_upgraded=tier_upgraded
    )
    
    return {
        'points_earned': points_earned,
        'new_balance': new_total,
        'tier': new_tier,
        'tier_upgraded': tier_upgraded
    }

def send_points_notification(member: dict, points_earned: int, new_total: int, 
                             new_tier: str, tier_upgraded: bool) -> dict:
    """Send WhatsApp loyalty update to customer."""
    
    if tier_upgraded:
        message = build_tier_upgrade_message(member['name'], new_tier, new_total)
    else:
        message = build_points_earned_message(
            member['name'], points_earned, new_total, new_tier
        )
    
    return requests.post(
        'https://app.rapiwa.com/send-message',
        headers={'Authorization': f'Bearer {RAPIWA_API_KEY}'},
        json={'number': member['phone'], 'message': message}
    ).json()

def build_points_earned_message(name: str, points_earned: int, 
                                total: int, tier: str) -> str:
    """Build the standard points earned WhatsApp message."""
    next_tier_info = get_next_tier_info(total, tier)
    
    return (
        f"Points Earned! 🌟\n\n"
        f"Hi {name}!\n\n"
        f"+{points_earned} points for your purchase\n"
        f"Total balance: *{total:,} points*\n"
        f"Tier: {tier} Member\n\n"
        f"{next_tier_info}\n\n"
        f"Check your balance: https://yourstore.com/loyalty\n"
        f"Reply REDEEM to use your points."
    )

def build_tier_upgrade_message(name: str, new_tier: str, total: int) -> str:
    """Build the tier upgrade congratulations message."""
    tier_benefits = {
        'Silver': '1.5x points on every purchase + free returns',
        'Gold': '2x points + free shipping on all orders + early sale access',
        'Platinum': '3x points + dedicated VIP support + monthly surprise gift'
    }
    
    return (
        f"🎉 Congratulations! You've been upgraded!\n\n"
        f"Hi {name}!\n\n"
        f"You've reached *{new_tier} status* — amazing!\n"
        f"Total points: {total:,}\n\n"
        f"Your new {new_tier} benefits:\n"
        f"✨ {tier_benefits.get(new_tier, 'Premium member benefits')}\n\n"
        f"Thank you for your loyalty! We truly appreciate you. 💛\n"
        f"→ https://yourstore.com/loyalty/benefits"
    )

Step 3: Balance Check via WhatsApp (Conversational)

Set up a webhook to handle balance check replies:

def handle_loyalty_whatsapp_reply(phone: str, message: str) -> None:
    """Process incoming WhatsApp messages for the loyalty program."""
    message_upper = message.strip().upper()
    
    member = get_member_by_phone(phone)
    if not member:
        send_reply(phone, "You're not enrolled in our loyalty program yet. Join at https://yourstore.com/loyalty")
        return
    
    if message_upper == 'BALANCE':
        reply = (
            f"*Your Loyalty Balance* 🌟\n\n"
            f"Name: {member['name']}\n"
            f"Points: *{member['total_points']:,}*\n"
            f"Tier: *{member['tier']} Member*\n\n"
            f"Redeem: 100 points = $1 discount\n"
            f"Redeem at checkout: https://yourstore.com/loyalty/redeem"
        )
    
    elif message_upper == 'REDEEM':
        if member['total_points'] >= 500:  # Minimum 500 points = $5
            code = generate_redemption_code(member['customer_id'], member['total_points'])
            reply = (
                f"Your Reward Code 🎁\n\n"
                f"Code: *{code}*\n"
                f"Value: ${member['total_points'] // 100} off your next order\n"
                f"Valid for 7 days\n\n"
                f"Apply at checkout: https://yourstore.com?reward={code}"
            )
        else:
            points_needed = 500 - member['total_points']
            reply = f"You need {points_needed} more points to redeem (minimum 500). Keep shopping to earn more! 🛍️"
    
    else:
        reply = (
            f"Hi {member['name']}! Reply with:\n\n"
            f"*BALANCE* — check your points\n"
            f"*REDEEM* — get a reward code"
        )
    
    send_reply(phone, reply)

def send_reply(phone: str, message: str) -> None:
    requests.post(
        'https://app.rapiwa.com/send-message',
        headers={'Authorization': f'Bearer {RAPIWA_API_KEY}'},
        json={'number': phone, 'message': message}
    )

Step 4: Automate Monthly Points Summary

Send a monthly summary to keep members engaged:

"Hi Sarah! Your February loyalty summary 📊

Points earned: 350
Points redeemed: 200
Current balance: *1,250 points* = $12.50 in rewards

Status: *Silver Member*
Progress to Gold: 750 points needed

Top tip: Double points week starts March 1!
→ https://yourstore.com/loyalty"

Test cURL:

curl -X POST https://app.rapiwa.com/send-message \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "number": "8801234567890",
    "message": "Points Earned! 🌟\n\nHi Sarah!\n\n+150 points for your purchase\nTotal balance: *1,250 points*\nTier: Silver Member\n\n750 more points to Gold status (2x earn rate!).\n\nCheck balance: yourstore.com/loyalty\nReply REDEEM to use points."
  }'

Results You Can Expect

  • 25–35% higher repeat purchase rate for loyalty members vs non-members
  • 3–5x higher engagement with WhatsApp loyalty updates vs email
  • 15–20% increase in average order value (loyalty members spend more per order)
  • 50% lower customer acquisition cost via loyalty-driven referrals

FAQ

Is there a ready-made loyalty program that integrates with Rapiwa? Not yet — a dedicated loyalty module is on Rapiwa's roadmap. For now, build with the database + Python pattern in this guide, or use n8n to connect your existing loyalty platform (like Smile.io or Yotpo) to Rapiwa.

Does Rapiwa charge per loyalty notification? No. Rapiwa charges $5/month flat with no per-message fees.

Can I use WhatsApp as the primary loyalty communication channel (replacing email)? Yes. WhatsApp's 98% open rate makes it significantly more effective than email for loyalty communications. Collect WhatsApp opt-in at enrollment and use it as the primary channel.

How do I prevent points abuse (earning and immediately redeeming)? Set a minimum points balance before redemption (e.g., 500 points), require a minimum order value for earning, and flag suspicious activity (large orders followed by immediate redemption) for manual review.

Can this loyalty program handle thousands of members? Yes. The database-backed design scales to any volume. Rapiwa sends one message per API call — use a job queue (Celery, Bull) for bulk notifications to thousands of members.