API Documentation

Welcome to the Pewang Gateway API. Build powerful messaging experiences and secure authentication flows using your own devices.

Base URL: https://gateway.pewang.company/api

WhatsApp

Send text, images, PDFs, videos via your connected WhatsApp numbers.

SMS

Send SMS via your connected Android gateway devices.

SIMCall Verifyβ„’

SIM-based phone verification. Impossible to fake, 10 credits per verify.

Broadcast

Send bulk messages to multiple recipients with queue management.

Authentication

All API requests must include your API Key. You can pass it in three ways:

MethodHowExample
Header (recommended) x-api-key header x-api-key: sk_live_abc123...
Query Parameter ?secret= in URL /api/sendSMS?secret=sk_live_abc123...
Request Body secret field in JSON body {"secret": "sk_live_abc123..."}

Generate your API key from the Developer Console β†’ Settings β†’ API Keys.

Security: Never expose your API key in client-side code. Always call from your server backend.
curl -X POST https://gateway.pewang.company/api/sendSMS \
  -H "x-api-key: sk_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{"to": "254712345678", "msg": "Hello!", "client": "my_device"}'

Client ID

The client parameter identifies which connected WhatsApp number or SMS gateway device should send your message.

Finding Your Client ID

You can find your Client ID in the Developer Console:

  1. Go to console.pewang.company
  2. Navigate to WhatsApp section
  3. Each connected number displays its Client ID with a copy button

The Client ID looks like: wa_abc123xyz or a custom name you've set.

Send SMS

Send a standard SMS message via your connected Android Gateway device.

POST /api/sendSMS
GET /api/sendSMS

Parameters

Parameter Type Required Description
to string Yes Recipient phone number (e.g., 254712345678)
msg string Yes Message content
sim int No SIM slot (0 or 1). Defaults to system default.
client string Yes Client ID of the sender device (from your dashboard)

Response

{
  "success": true,
  "message": "Message queued",
  "id": "550e8400-e29b-41d4-a716-446655440000"
}

Send WhatsApp

Send text messages, images, videos, and documents (PDF, DOCX, etc.) via your connected WhatsApp client.

POST /api/sendWhatsApp
GET /api/sendWhatsApp

Parameters

Parameter Type Required Description
to string Yes Recipient phone number (e.g., 254712345678 or 0712345678)
msg string Yes Text content or URL/path to media file
client string Yes Client ID of the sender (from your dashboard)
type string No Message type: image, video, or document. Omit for plain text.
caption string No Caption text to display with media (images, videos, documents)
fileName string No Display filename for documents (e.g., report.pdf)
mimetype string No MIME type for documents. Default: application/pdf

Send a Text Message

curl -X POST https://gateway.pewang.company/api/sendWhatsApp \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": "254712345678", "msg": "Hello from our system! πŸ‘‹", "client": "my_shop"}'

Response

{
  "success": true,
  "clientId": "my_shop",
  "to": "254712345678@s.whatsapp.net",
  "status": "queued",
  "queueSize": 1
}

πŸ“Έ Send Media (Images, Videos, PDFs)

The same /api/sendWhatsApp endpoint handles all media types. Set the type parameter and pass a URL or file path as msg.

Send an Image

curl -X POST https://gateway.pewang.company/api/sendWhatsApp \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "254712345678",
    "client": "hotel_reception",
    "type": "image",
    "msg": "https://myhotel.com/images/room-deluxe.jpg",
    "caption": "🏨 Here is your room! Check-in is at 2 PM."
  }'

Send a PDF Document

curl -X POST https://gateway.pewang.company/api/sendWhatsApp \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "254712345678",
    "client": "hotel_reception",
    "type": "document",
    "msg": "https://myhotel.com/reports/daily-2026-04-15.pdf",
    "caption": "πŸ“Š Daily Occupancy Report - April 15, 2026",
    "fileName": "daily_report_2026-04-15.pdf",
    "mimetype": "application/pdf"
  }'

Send a Video

curl -X POST https://gateway.pewang.company/api/sendWhatsApp \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "254712345678",
    "client": "hotel_reception",
    "type": "video",
    "msg": "https://myhotel.com/videos/room-tour.mp4",
    "caption": "πŸŽ₯ Take a virtual tour of our suites!"
  }'

Supported Media Types

TypeFormatsMax SizeUse Case
imageJPEG, PNG, WebP16 MBPhotos, flyers, receipts
videoMP4, 3GP16 MBTours, demos, tutorials
documentPDF, DOCX, XLSX, CSV100 MBReports, invoices, contracts
Tip: You can pass either a public URL (https://...) or a local file path on the server as the msg value.

πŸ“’ Broadcast Messages

Send the same message to multiple recipients. Messages are queued and sent with human-like delays to prevent WhatsApp rate limiting.

POST /api/broadcast

Parameters

ParameterTypeRequiredDescription
recipientsarrayYesArray of phone numbers
messagestringYesMessage to send to all recipients
clientstringYesClient ID of the sender

Example

curl -X POST https://gateway.pewang.company/api/broadcast \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "client": "hotel_reception",
    "message": "πŸŽ‰ Happy holidays from our hotel! Enjoy 20% off your next stay.",
    "recipients": ["254712345678", "254798765432", "254700111222"]
  }'
Credits: Each recipient costs 1 credit. A broadcast to 100 recipients = 100 credits.

πŸ’° Credits & Pricing

Pewang Gateway uses a credit-based billing system. Credits are deducted per action:

ActionCostDescription
SMS Send1 creditEach outbound SMS
WhatsApp Send1 creditEach outbound WhatsApp (text or media)
WhatsApp Bot Reply0.5 creditsAuto-replies from your chatbot
AI Assistant5 creditsAI-generated responses
SIMCall Verifyβ„’10 creditsPer successful phone verification
Broadcast1 credit/msgPer recipient in a broadcast
Free Tier: New accounts get 1 free credit to test the API. Top up via the Developer Console.

Low Balance Alerts

You'll receive automatic WhatsApp notifications when your balance crosses these thresholds:

  • 500 credits β€” Heads-up notice
  • 100 credits β€” Low balance warning
  • 20 credits β€” Urgent: very low
  • 0 credits β€” Critical: services paused

πŸ“Š Billing API

Programmatically monitor your balance and usage from your application.

Check Balance

GET /api/credits/balance-check

Returns your current balance, warning level, and plan.

curl https://gateway.pewang.company/api/credits/balance-check \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "balance": 342,
  "sufficient": true,
  "warning": null,
  "plan": "Pro"
}

Usage Report

GET /api/user/usage-report

Returns per-type, per-day usage breakdown for a date range.

curl "https://gateway.pewang.company/api/user/usage-report?startDate=2026-04-01&endDate=2026-04-15" \
  -H "Authorization: Bearer YOUR_TOKEN"

Billing Summary

GET /api/user/billing-summary

Returns balance + full pricing table for display in your app.

curl https://gateway.pewang.company/api/user/billing-summary \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "balance": 342,
  "warning": null,
  "plan": "Pro",
  "pricing": {
    "SMS_SEND": 1,
    "WHATSAPP_SEND": 1,
    "WHATSAPP_BOT_REPLY": 0.5,
    "AI_GENERATION": 5,
    "SIMCALL_VERIFY": 10
  }
}

πŸ’³ M-Pesa STK Push (Daraja)

Use this API to request payment from a customer directly from your chatbot or backend. Each gateway account can configure its own Daraja credentials in Console β†’ Settings β†’ M-Pesa Daraja.

Integration-grade requirement: M-Pesa endpoints require x-api-key and Idempotency-Key headers. Do not pass API keys in query/body for payment routes.
POST /api/mpesa/stk-push

Auth: x-api-key

Parameters

ParameterTypeRequiredDescription
phonestringYesCustomer phone (07... or 2547...)
amountKesnumberYesAmount in KES
accountReferencestringNoOrder/account reference (max 12 chars recommended)
transactionDescstringNoShort payment description
clientIdstringNoYour WhatsApp client context for tracing

Example

curl -X POST https://gateway.pewang.company/api/mpesa/stk-push \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Idempotency-Key: 7f4a6f03-15e4-42a0-a40c-12f91234abcd" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "254712345678",
    "amountKes": 1500,
    "accountReference": "ORDER1001",
    "transactionDesc": "Shoes",
    "clientId": "shop_whatsapp"
  }'

Response

{
  "success": true,
  "message": "Check your phone for M-Pesa PIN prompt.",
  "data": {
    "checkoutRequestId": "ws_CO_260420261234567890123",
    "merchantRequestId": "29115-34620561-1",
    "amountKes": 1500,
    "phone254": "254712345678"
  }
}

Check STK status

GET /api/mpesa/stk-status/:checkoutRequestId

Use this if you want polling fallback in addition to webhook events.

Receipt Download

GET /api/mpesa/receipt/:checkoutRequestId

Returns an official PDF receipt for a successful transaction you own.

Reconciliation APIs

GET /api/mpesa/transactions?from=&to=&status=&limit=

Returns transaction rows for external systems (loan, ERP, bank core, collections).

GET /api/mpesa/reconciliation/summary?from=&to=

Returns totals for success/pending/failed/mismatch in a reporting window.

Integration Downloads

πŸ§ͺ Testing with Postman

Postman is the easiest way to test the Pewang Gateway API. Follow these steps:

Step 1: Set Up Environment

Create a Postman Environment with these variables:

VariableValue
base_urlhttps://gateway.pewang.company/api
api_keyYour API key from the Developer Console
client_idYour connected device Client ID

Step 2: Send a WhatsApp Text

Create a new request in Postman:

  • Method: POST
  • URL: {{base_url}}/sendWhatsApp
  • Headers: x-api-key: {{api_key}} and Content-Type: application/json

Body (raw JSON):

{
  "to": "254712345678",
  "msg": "Hello from Postman! πŸš€",
  "client": "{{client_id}}"
}

Step 3: Send an Image

Same setup, but change the body:

{
  "to": "254712345678",
  "msg": "https://picsum.photos/800/600",
  "client": "{{client_id}}",
  "type": "image",
  "caption": "Test image from Postman πŸ“Έ"
}

Step 4: Send a PDF Document

{
  "to": "254712345678",
  "msg": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "client": "{{client_id}}",
  "type": "document",
  "caption": "Quarterly Report Q1 2026",
  "fileName": "Q1_Report_2026.pdf",
  "mimetype": "application/pdf"
}

Step 5: Send SMS

  • Method: POST
  • URL: {{base_url}}/sendSMS
{
  "to": "254712345678",
  "msg": "Your OTP is 4829. Valid for 5 minutes.",
  "client": "{{client_id}}"
}
Quick Test (GET): You can also test directly in your browser:
https://gateway.pewang.company/api/sendWhatsApp?to=254712345678&msg=Hello&client=YOUR_CLIENT&secret=YOUR_KEY

⚠️ Error Codes

All API responses include a success boolean. When false, an error field explains the issue.

HTTP CodeErrorMeaningFix
401Missing API KeyNo secret or x-api-key providedAdd your API key to the request
403Invalid API KeyAPI key is wrong or deactivatedCheck your key in the Console
403Insufficient creditsBalance too low for this actionTop up credits in the Console
403Account suspendedYour account has been disabledContact support
400Missing required fieldsto, msg, or client is missingInclude all required params
500Client not connectedThe WhatsApp device is offlineRe-scan QR code in Console

Error Response Format

{
  "success": false,
  "error": "Insufficient credits",
  "required": 1,
  "balance": 0
}

πŸ” SIMCall Verifyβ„’

Secure, SIM-based authentication that's impossible to fake. 10 credits per successful verification.

Pay Per Success

Only pay 10 credits when a user is successfully verified. No setup fees.

Impossible to Fake

Requires physical possession of the SIM card to answer the verification call.

Instant

3-25 second verification times. Faster than waiting for SMS delivery.

Global

Works in any country. No carrier integration or sender ID registration needed.

Billing Requirement: You must have a minimum balance of 10 credits to initiate a verification.

How It Works

SIMCall Verifyβ„’ uses a challenge-response protocol based on call duration timing:

  1. Challenge Created - Server generates random target duration (3-25s)
  2. Call Placed - User receives call from verification system
  3. User Answers & Holds - Must hold call for exact duration
  4. User Hangs Up - Timing measured server-side
  5. Validation - Duration within window = verified βœ…
// Example Challenge
{
  "challengeId": "abc-123",
  "targetDuration": 12000,  // 12 seconds
  "minTime": 11000,           // 11s (tolerance: Β±1s)
  "maxTime": 13000,           // 13s
  "expiresAt": 1735672030000  // 30s validity
}

πŸ›‘οΈ Security Features

Feature Description
Rate Limiting 3 failed attempts in 5 min β†’ 30 min cooldown
SIM Pool Multiple devices, health monitoring, auto-recovery
Queue System FIFO processing with ETA estimation
State Machine 13-state strict flow prevents logic errors
Challenge Expiry 30-second validity window

Integration Guide

Integrate SIMCall Verifyβ„’ in 3 simple steps:

Step 1: Initiate Verification

POST /api/simcall/initiate
Parameter Type Required Description
phoneNumber string Yes Phone number to verify (international format: +254711...)

Response:

{
  "success": true,
  "challengeId": "550e8400-e29b-41d4-a716-446655440000",
  "queuePosition": 1,
  "eta": 15000,  // milliseconds
  "message": "Verification queued. You will receive a call shortly."
}

Step 2: Poll for Status

GET /api/simcall/status/:challengeId

Response:

{
  "success": true,
  "status": "SUCCESS",  // or "PENDING", "FAILED"
  "verified": true,
  "reason": "Verified"
}

Step 3: Handle Result

Poll every 2-3 seconds until status is SUCCESS or FAILED.

βœ… SUCCESS - User verified, grant access
❌ FAILED - Verification failed (wrong timing, expired, etc.)

Code Examples

Ready-to-use examples in popular languages:

JavaScript / Node.js

const axios = require('axios');

async function verifySIMCall(phoneNumber) {
  const API_KEY = 'YOUR_API_KEY';
  const BASE_URL = 'https://gateway.pewang.company/api';

  // Step 1: Initiate verification
  const { data } = await axios.post(
    `${BASE_URL}/simcall/initiate`,
    { phoneNumber },
    { headers: { 'x-api-key': API_KEY } }
  );

  if (!data.success) throw new Error(data.error);

  const challengeId = data.challengeId;
  console.log(`Verification queued. ETA: ${data.eta / 1000}s`);

  // Step 2: Poll for status
  while (true) {
    await new Promise(r => setTimeout(r, 3000)); // Wait 3s

    const { data: status } = await axios.get(
      `${BASE_URL}/simcall/status/${challengeId}`,
      { headers: { 'x-api-key': API_KEY } }
    );

    if (status.status === 'SUCCESS') {
      console.log('βœ… Verified!');
      return true;
    }

    if (status.status === 'FAILED') {
      console.log(`❌ Failed: ${status.reason}`);
      return false;
    }
  }
}

// Usage
verifySIMCall('+254711234567');

Python

import requests
import time

def verify_simcall(phone_number):
    API_KEY = 'YOUR_API_KEY'
    BASE_URL = 'https://gateway.pewang.company/api'
    headers = {'x-api-key': API_KEY}

    # Step 1: Initiate
    response = requests.post(
        f'{BASE_URL}/simcall/initiate',
        json={'phoneNumber': phone_number},
        headers=headers
    )
    data = response.json()

    if not data['success']:
        raise Exception(data['error'])

    challenge_id = data['challengeId']
    print(f"Queued. ETA: {data['eta'] / 1000}s")

    # Step 2: Poll
    while True:
        time.sleep(3)
        
        status = requests.get(
            f'{BASE_URL}/simcall/status/{challenge_id}',
            headers=headers
        ).json()

        if status['status'] == 'SUCCESS':
            print('βœ… Verified!')
            return true

        if status['status'] == 'FAILED':
            print(f"❌ Failed: {status['reason']}")
            return false

# Usage
verify_simcall('+254711234567')

PHP

<?php
function verifySIMCall($phoneNumber) {
    $apiKey = 'YOUR_API_KEY';
    $baseUrl = 'https://gateway.pewang.company/api';

    // Step 1: Initiate
    $ch = curl_init("$baseUrl/simcall/initiate");
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'phoneNumber' => $phoneNumber
    ]));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        "x-api-key: $apiKey"
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    $response = json_decode(curl_exec($ch), true);
    curl_close($ch);

    if (!$response['success']) {
        throw new Exception($response['error']);
    }

    $challengeId = $response['challengeId'];
    echo "Queued. ETA: " . ($response['eta'] / 1000) . "s\n";

    // Step 2: Poll
    while (true) {
        sleep(3);

        $ch = curl_init("$baseUrl/simcall/status/$challengeId");
        curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-api-key: $apiKey"]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $status = json_decode(curl_exec($ch), true);
        curl_close($ch);

        if ($status['status'] === 'SUCCESS') {
            echo "βœ… Verified!\n";
            return true;
        }

        if ($status['status'] === 'FAILED') {
            echo "❌ Failed: {$status['reason']}\n";
            return false;
        }
    }
}

// Usage
verifySIMCall('+254711234567');
?>

Java

import java.net.http.*;
import com.google.gson.*;

public class SIMCallVerify {
    private static final String API_KEY = "YOUR_API_KEY";
    private static final String BASE_URL = "https://gateway.pewang.company/api";

    public static boolean verify(String phoneNumber) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        Gson gson = new Gson();

        // Step 1: Initiate
        String json = gson.toJson(Map.of("phoneNumber", phoneNumber));
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(BASE_URL + "/simcall/initiate"))
            .header("x-api-key", API_KEY)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(json))
            .build();

        HttpResponse<String> response = client.send(request, 
            HttpResponse.BodyHandlers.ofString());
        JsonObject data = gson.fromJson(response.body(), JsonObject.class);

        String challengeId = data.get("challengeId").getAsString();
        System.out.println("Queued. ETA: " + data.get("eta").getAsInt() / 1000 + "s");

        // Step 2: Poll
        while (true) {
            Thread.sleep(3000);

            request = HttpRequest.newBuilder()
                .uri(URI.create(BASE_URL + "/simcall/status/" + challengeId))
                .header("x-api-key", API_KEY)
                .GET()
                .build();

            response = client.send(request, HttpResponse.BodyHandlers.ofString());
            JsonObject status = gson.fromJson(response.body(), JsonObject.class);

            String state = status.get("status").getAsString();
            if (state.equals("SUCCESS")) {
                System.out.println("βœ… Verified!");
                return true;
            }
            if (state.equals("FAILED")) {
                System.out.println("❌ Failed");
                return false;
            }
        }
    }
}

C# / .NET

using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

public class SIMCallVerify
{
    private const string API_KEY = "YOUR_API_KEY";
    private const string BASE_URL = "https://gateway.pewang.company/api";

    public static async Task<bool> Verify(string phoneNumber)
    {
        using var client = new HttpClient();
        client.DefaultRequestHeaders.Add("x-api-key", API_KEY);

        // Step 1: Initiate
        var payload = new { phoneNumber };
        var content = new StringContent(
            JsonSerializer.Serialize(payload),
            System.Text.Encoding.UTF8,
            "application/json"
        );

        var response = await client.PostAsync(
            $"{BASE_URL}/simcall/initiate", 
            content
        );
        var data = await JsonSerializer.DeserializeAsync<JsonElement>(
            await response.Content.ReadAsStreamAsync()
        );

        var challengeId = data.GetProperty("challengeId").GetString();
        Console.WriteLine($"Queued. ETA: {data.GetProperty(\"eta\").GetInt32() / 1000}s");

        // Step 2: Poll
        while (true)
        {
            await Task.Delay(3000);

            response = await client.GetAsync(
                $"{BASE_URL}/simcall/status/{challengeId}"
            );
            var status = await JsonSerializer.DeserializeAsync<JsonElement>(
                await response.Content.ReadAsStreamAsync()
            );

            var state = status.GetProperty("status").GetString();
            if (state == "SUCCESS")
            {
                Console.WriteLine("βœ… Verified!");
                return true;
            }
            if (state == "FAILED")
            {
                Console.WriteLine("❌ Failed");
                return false;
            }
        }
    }
}

Go

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "time"
)

const (
    API_KEY  = "YOUR_API_KEY"
    BASE_URL = "https://gateway.pewang.company/api"
)

func verifySIMCall(phoneNumber string) (bool, error) {
    client := &http.Client{}

    // Step 1: Initiate
    payload, _ := json.Marshal(map[string]string{
        "phoneNumber": phoneNumber,
    })
    req, _ := http.NewRequest("POST", BASE_URL+"/simcall/initiate", 
        bytes.NewBuffer(payload))
    req.Header.Set("x-api-key", API_KEY)
    req.Header.Set("Content-Type", "application/json")

    resp, err := client.Do(req)
    if err != nil {
        return false, err
    }
    defer resp.Body.Close()

    var data map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&data)

    challengeId := data["challengeId"].(string)
    fmt.Printf("Queued. ETA: %.0fs\n", data["eta"].(float64)/1000)

    // Step 2: Poll
    for {
        time.Sleep(3 * time.Second)

        req, _ = http.NewRequest("GET", 
            fmt.Sprintf("%s/simcall/status/%s", BASE_URL, challengeId), nil)
        req.Header.Set("x-api-key", API_KEY)

        resp, _ = client.Do(req)
        json.NewDecoder(resp.Body).Decode(&data)
        resp.Body.Close()

        status := data["status"].(string)
        if status == "SUCCESS" {
            fmt.Println("βœ… Verified!")
            return true, nil
        }
        if status == "FAILED" {
            fmt.Println("❌ Failed")
            return false, nil
        }
    }
}

Ruby

require 'net/http'
require 'json'

def verify_simcall(phone_number)
  api_key = 'YOUR_API_KEY'
  base_url = 'https://gateway.pewang.company/api'

  # Step 1: Initiate
  uri = URI("#{base_url}/simcall/initiate")
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri.path)
  request['x-api-key'] = api_key
  request['Content-Type'] = 'application/json'
  request.body = { phoneNumber: phone_number }.to_json

  response = http.request(request)
  data = JSON.parse(response.body)

  challenge_id = data['challengeId']
  puts "Queued. ETA: #{data['eta'] / 1000}s"

  # Step 2: Poll
  loop do
    sleep(3)

    uri = URI("#{base_url}/simcall/status/#{challenge_id}")
    request = Net::HTTP::Get.new(uri.path)
    request['x-api-key'] = api_key

    response = http.request(request)
    status = JSON.parse(response.body)

    if status['status'] == 'SUCCESS'
      puts 'βœ… Verified!'
      return true
    elsif status['status'] == 'FAILED'
      puts "❌ Failed: #{status['reason']}"
      return false
    end
  end
end

# Usage
verify_simcall('+254711234567')

Best Practices

βœ… Do's

  • Use international format - Always include country code (+254...)
  • Poll every 2-3 seconds - Don't overwhelm the server
  • Handle rate limits gracefully - Show user-friendly error messages
  • Set timeout - Stop polling after 60 seconds
  • Show ETA to users - Use the eta field for better UX
  • Log failures - Track verification failures for debugging

❌ Don'ts

  • Don't poll too frequently - < 2 seconds wastes resources
  • Don't retry immediately on failure - Respect rate limits
  • Don't expose API keys - Keep them server-side only
  • Don't skip validation - Always check success field
  • Don't use for high-frequency auth - Best for login, not every API call

🎯 Use Cases

Use Case Why SIMCall Verify?
User Login Passwordless, zero-cost authentication
Phone Verification Prove SIM ownership without SMS costs
2FA / MFA Second factor that can't be phished
Account Recovery Verify identity for password reset
High-Value Transactions Extra security for payments, transfers

πŸ’‘ Pro Tip

Combine SIMCall Verify with traditional methods for a hybrid approach: Use SIMCall for high-security actions (login, payments) and SMS/email for low-security notifications.

Webhooks

Configure a Webhook URL in your dashboard settings to receive real-time updates about message status, incoming messages, and billing events.

Pewang Gateway sends POST requests to your URL with a JSON body.

Deliveries include event metadata headers and automatic retry attempts for transient failures.

Setup: Go to Developer Console β†’ Settings β†’ Webhook URL and enter your endpoint + optional webhook secret for signed payload verification.

Webhook Headers

HeaderDescription
X-Pewang-Event-IdUnique event delivery ID
X-Pewang-TimestampISO timestamp used in signature payload
X-Pewang-RetryRetry counter (1..3)
X-Pewang-Signaturesha256=... HMAC signature if secret configured

Webhook Events

Message Sent

Triggered when a message is successfully sent/queued.

{
  "event": "message.sent",
  "timestamp": "2026-04-15T10:00:00Z",
  "data": {
    "id": "550e8400-e29b-41d4...",
    "to": "254712345678",
    "type": "WHATSAPP",
    "status": "QUEUED"
  }
}

Message Received (Incoming)

Triggered when a customer sends a message to your connected number.

{
  "event": "message.received",
  "timestamp": "2026-04-15T10:01:00Z",
  "data": {
    "type": "WHATSAPP",
    "from": "254712345678",
    "body": "I'd like to book a room",
    "clientId": "hotel_reception"
  }
}

Low Balance Alert

Triggered when your credit balance drops below a threshold.

{
  "event": "balance.low",
  "timestamp": "2026-04-15T14:30:00Z",
  "data": {
    "currentBalance": 18,
    "threshold": 20,
    "severity": "critical"
  }
}

M-Pesa Webhook Events

When Daraja STK is enabled for your account, the gateway can push payment lifecycle events to your webhook URL.

STK Initiated

{
  "event": "mpesa.stk_initiated",
  "timestamp": "2026-04-26T08:40:00Z",
  "data": {
    "checkoutRequestId": "ws_CO_...",
    "merchantRequestId": "29115-...",
    "amountKes": 1500,
    "phone254": "254712345678",
    "clientId": "shop_whatsapp"
  }
}

STK Completed

{
  "event": "mpesa.stk_completed",
  "timestamp": "2026-04-26T08:41:10Z",
  "data": {
    "checkoutRequestId": "ws_CO_...",
    "mpesaReceiptNumber": "QWE12RTY34",
    "amountKes": 1500,
    "phone254": "254712345678",
    "status": "success"
  }
}

STK Failed

{
  "event": "mpesa.stk_failed",
  "timestamp": "2026-04-26T08:41:10Z",
  "data": {
    "checkoutRequestId": "ws_CO_...",
    "resultCode": 1032,
    "resultDesc": "Request cancelled by user",
    "amountKes": 1500
  }
}

πŸ’‘ Real-World Integration Examples

Here's how developers integrate the Pewang Gateway into real business systems:

🏨 Hotel Management System

// Node.js β€” Send check-in confirmation with room photo
const axios = require('axios');

async function sendCheckInConfirmation(guestPhone, guestName, roomNumber) {
  await axios.post('https://gateway.pewang.company/api/sendWhatsApp', {
    to: guestPhone,
    client: 'hotel_reception',
    type: 'image',
    msg: `https://myhotel.com/rooms/${roomNumber}/photo.jpg`,
    caption: `Welcome ${guestName}! 🏨\n\nRoom: ${roomNumber}\nCheck-in: 2:00 PM\nWiFi: HotelGuest / pass1234\n\nEnjoy your stay!`
  }, { headers: { 'x-api-key': process.env.PEWANG_KEY } });
}

// Send daily report PDF to hotel manager
async function sendDailyReport(managerPhone) {
  const today = new Date().toISOString().split('T')[0];
  await axios.post('https://gateway.pewang.company/api/sendWhatsApp', {
    to: managerPhone,
    client: 'hotel_reception',
    type: 'document',
    msg: `https://myhotel.com/api/reports/daily/${today}.pdf`,
    caption: `πŸ“Š Daily Report β€” ${today}`,
    fileName: `hotel_report_${today}.pdf`,
    mimetype: 'application/pdf'
  }, { headers: { 'x-api-key': process.env.PEWANG_KEY } });
}

πŸ›’ E-Commerce Order Notifications

# Python β€” Send order confirmation + invoice PDF
import requests

def send_order_confirmation(phone, order_id, total):
    requests.post('https://gateway.pewang.company/api/sendWhatsApp', json={
        'to': phone,
        'client': 'shop_whatsapp',
        'msg': f'βœ… Order #{order_id} confirmed!\n\nπŸ’° Total: KES {total:,.0f}\nπŸ“¦ Delivery: 2-3 business days\n\nThank you for shopping with us! πŸ›οΈ'
    }, headers={'x-api-key': 'sk_live_...'})

def send_invoice(phone, order_id):
    requests.post('https://gateway.pewang.company/api/sendWhatsApp', json={
        'to': phone,
        'client': 'shop_whatsapp',
        'type': 'document',
        'msg': f'https://myshop.co.ke/invoices/{order_id}.pdf',
        'caption': f'🧾 Invoice for Order #{order_id}',
        'fileName': f'invoice_{order_id}.pdf',
        'mimetype': 'application/pdf'
    }, headers={'x-api-key': 'sk_live_...'})

πŸ₯ Clinic Appointment Reminders

// PHP β€” Remind patients of upcoming appointments
<?php
function sendAppointmentReminder($phone, $doctor, $date, $time) {
    $ch = curl_init('https://gateway.pewang.company/api/sendWhatsApp');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'to' => $phone,
        'client' => 'clinic_wa',
        'msg' => "πŸ₯ *Appointment Reminder*\n\n"
            . "πŸ‘¨β€βš•οΈ Dr. $doctor\n"
            . "πŸ“… $date at $time\n\n"
            . "Reply YES to confirm or CANCEL to reschedule."
    ]));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'x-api-key: sk_live_...'
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $result = curl_exec($ch);
    curl_close($ch);
    return json_decode($result, true);
}
?>

πŸ“± Android App β€” Kotlin (OTP & Notifications)

// Kotlin β€” Send OTP via WhatsApp from your Android backend
import okhttp3.*
import org.json.JSONObject

class PewangGateway(private val apiKey: String) {
    private val client = OkHttpClient()
    private val baseUrl = "https://gateway.pewang.company/api"

    // Send OTP code via WhatsApp
    fun sendOTP(phone: String, otp: String) {
        val json = JSONObject().apply {
            put("to", phone)
            put("client", "my_app_wa")
            put("msg", "πŸ” *Your OTP Code*\n\n" +
                "Code: *$otp*\n" +
                "Valid for 5 minutes.\n\n" +
                "Do not share this code with anyone.")
        }
        val body = RequestBody.create(
            MediaType.parse("application/json"), json.toString()
        )
        val request = Request.Builder()
            .url("$baseUrl/sendWhatsApp")
            .addHeader("x-api-key", apiKey)
            .post(body).build()

        client.newCall(request).enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                println("βœ… OTP sent: ${response.body()?.string()}")
            }
            override fun onFailure(call: Call, e: IOException) {
                println("❌ Failed: ${e.message}")
            }
        })
    }

    // Send receipt image after payment
    fun sendReceipt(phone: String, receiptUrl: String, amount: String) {
        val json = JSONObject().apply {
            put("to", phone)
            put("client", "my_app_wa")
            put("type", "image")
            put("msg", receiptUrl)
            put("caption", "βœ… Payment of KES $amount received!\nThank you for your purchase.")
        }
        // ... same request pattern
    }
}

πŸ“± Flutter / Dart (Delivery Tracking)

// Flutter β€” Send delivery updates to customers
import 'package:http/http.dart' as http;
import 'dart:convert';

class PewangGateway {
  static const _baseUrl = 'https://gateway.pewang.company/api';
  final String apiKey;
  final String clientId;

  PewangGateway({required this.apiKey, required this.clientId});

  // Send order status update
  Future<bool> sendDeliveryUpdate(String phone, String orderId, String status) async {
    final messages = {
      'confirmed': 'βœ… Order #$orderId confirmed! Preparing now...',
      'preparing': 'πŸ‘¨β€πŸ³ Order #$orderId is being prepared!',
      'on_the_way': 'πŸš— Your rider is on the way with order #$orderId!',
      'delivered': 'πŸ“¦ Order #$orderId delivered! Thank you! ⭐',
    };

    final response = await http.post(
      Uri.parse('$_baseUrl/sendWhatsApp'),
      headers: {
        'x-api-key': apiKey,
        'Content-Type': 'application/json',
      },
      body: jsonEncode({
        'to': phone,
        'client': clientId,
        'msg': messages[status] ?? 'Order update for #$orderId',
      }),
    );

    return response.statusCode == 200;
  }

  // Send invoice PDF to customer
  Future<bool> sendInvoice(String phone, String invoiceUrl, String orderId) async {
    final response = await http.post(
      Uri.parse('$_baseUrl/sendWhatsApp'),
      headers: {'x-api-key': apiKey, 'Content-Type': 'application/json'},
      body: jsonEncode({
        'to': phone,
        'client': clientId,
        'type': 'document',
        'msg': invoiceUrl,
        'caption': '🧾 Invoice for Order #$orderId',
        'fileName': 'invoice_$orderId.pdf',
        'mimetype': 'application/pdf',
      }),
    );

    return response.statusCode == 200;
  }
}

πŸ“± React Native (Food Ordering App)

// React Native β€” WhatsApp notifications for a food ordering app
const PEWANG_API = 'https://gateway.pewang.company/api';
const API_KEY = 'sk_live_...';
const CLIENT = 'restaurant_wa';

// Call from your backend (NOT from the React Native app directly!)
export const sendOrderConfirmation = async (phone, items, total) => {
  const itemList = items.map(i => `β€’ ${i.name} x${i.qty}`).join('\n');
  
  await fetch(`${PEWANG_API}/sendWhatsApp`, {
    method: 'POST',
    headers: {
      'x-api-key': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      to: phone,
      client: CLIENT,
      msg: `πŸ• *Order Confirmed!*\n\n${itemList}\n\nπŸ’° Total: KES ${total}\n⏱️ Ready in 25-30 mins\n\nTrack: https://myapp.com/track/ORDER123`
    })
  });
};

// Send menu image with daily specials
export const sendDailySpecials = async (phone, menuImageUrl) => {
  await fetch(`${PEWANG_API}/sendWhatsApp`, {
    method: 'POST',
    headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      to: phone, client: CLIENT,
      type: 'image',
      msg: menuImageUrl,
      caption: "🍽️ Today's Specials! Order now and get 10% off πŸŽ‰"
    })
  });
};

🏫 School Management System

// Node.js β€” Notify parents of student activities
const axios = require('axios');
const PEWANG = { url: 'https://gateway.pewang.company/api', key: 'sk_live_...' };

// Fee reminder with payment slip
async function sendFeeReminder(parentPhone, studentName, amount, term) {
  await axios.post(`${PEWANG.url}/sendWhatsApp`, {
    to: parentPhone,
    client: 'school_admin',
    type: 'document',
    msg: `https://school.example.com/fees/${studentName}/invoice.pdf`,
    caption: `πŸ“š *Fee Reminder β€” ${term}*\n\nStudent: ${studentName}\nAmount Due: KES ${amount}\nDeadline: End of month\n\nPay via M-Pesa: 522522, A/C: SCHOOL`,
    fileName: `fee_invoice_${term}.pdf`
  }, { headers: { 'x-api-key': PEWANG.key } });
}

// Exam results broadcast to all parents
async function broadcastExamResults(parentPhones) {
  await axios.post(`${PEWANG.url}/broadcast`, {
    client: 'school_admin',
    message: 'πŸ“Š *End of Term Results*\n\nResults are now available on the parent portal.\n\nπŸ”— https://portal.school.example.com/results\n\nLogin with your registered email.',
    recipients: parentPhones
  }, { headers: { 'x-api-key': PEWANG.key } });
}

🏠 Real Estate / Property Management

# Python β€” Property listing notifications with photos
import requests

def send_property_listing(phone, property_data):
    """Send new property listing with photo to potential buyer"""
    API = {'url': 'https://gateway.pewang.company/api', 'key': 'sk_live_...'}
    
    # Send property photo
    requests.post(f"{API['url']}/sendWhatsApp", json={
        'to': phone,
        'client': 'agency_wa',
        'type': 'image',
        'msg': property_data['photo_url'],
        'caption': (
            f"🏠 *New Listing: {property_data['title']}*\n\n"
            f"πŸ“ {property_data['location']}\n"
            f"πŸ›οΈ {property_data['bedrooms']} Bed | 🚿 {property_data['bathrooms']} Bath\n"
            f"πŸ’° KES {property_data['price']:,.0f}/month\n\n"
            f"Available from: {property_data['available_date']}\n"
            f"Reply BOOK to schedule a viewing!"
        )
    }, headers={'x-api-key': API['key']})

def send_rent_reminder(phone, tenant_name, amount, unit):
    """Monthly rent reminder"""
    requests.post('https://gateway.pewang.company/api/sendWhatsApp', json={
        'to': phone,
        'client': 'agency_wa',
        'msg': (
            f"🏘️ *Rent Reminder*\n\n"
            f"Dear {tenant_name},\n"
            f"Unit: {unit}\n"
            f"Amount: KES {amount:,.0f}\n"
            f"Due: 5th of this month\n\n"
            f"M-Pesa Paybill: 247247\nA/C: {unit}"
        )
    }, headers={'x-api-key': 'sk_live_...'})

πŸ”§ SaaS Platform (Multi-Tenant)

// Go β€” SaaS platform integrating Pewang for customer notifications
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

type PewangClient struct {
    APIKey   string
    ClientID string
}

func (p *PewangClient) SendWhatsApp(to, msg string) error {
    payload, _ := json.Marshal(map[string]string{
        "to": to, "client": p.ClientID, "msg": msg,
    })
    req, _ := http.NewRequest("POST",
        "https://gateway.pewang.company/api/sendWhatsApp",
        bytes.NewBuffer(payload))
    req.Header.Set("x-api-key", p.APIKey)
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil { return err }
    defer resp.Body.Close()
    fmt.Printf("Sent to %s: %d\n", to, resp.StatusCode)
    return nil
}

func SendDocument(p *PewangClient, to, docUrl, caption, fileName string) error {
    payload, _ := json.Marshal(map[string]string{
        "to": to, "client": p.ClientID,
        "type": "document", "msg": docUrl,
        "caption": caption, "fileName": fileName,
        "mimetype": "application/pdf",
    })
    req, _ := http.NewRequest("POST",
        "https://gateway.pewang.company/api/sendWhatsApp",
        bytes.NewBuffer(payload))
    req.Header.Set("x-api-key", p.APIKey)
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil { return err }
    defer resp.Body.Close()
    return nil
}

// Usage: Each SaaS tenant gets their own Pewang client
func main() {
    hotel := &PewangClient{APIKey: "sk_live_HOTEL_KEY", ClientID: "hotel_wa"}
    shop := &PewangClient{APIKey: "sk_live_SHOP_KEY", ClientID: "shop_wa"}

    hotel.SendWhatsApp("254712345678", "Welcome to our hotel! 🏨")
    shop.SendWhatsApp("254798765432", "Your order is ready! πŸ“¦")
}

🀝 Harambee & Community Fundraising

// Node.js β€” collect Harambee contributions with references
const axios = require('axios');

async function collectHarambee(phone, amountKes) {
  const idempotencyKey = `harambee-${Date.now()}-${phone}`;
  return axios.post('https://gateway.pewang.company/api/mpesa/stk-push', {
    phone,
    amountKes,
    accountReference: 'HARAMBEE-APR2026',
    transactionDesc: 'Fundraising',
    clientId: 'harambee_bot'
  }, {
    headers: {
      'x-api-key': process.env.PEWANG_KEY,
      'Idempotency-Key': idempotencyKey
    }
  });
}

🎟️ Events Ticketing + Gate Reconciliation

# Python β€” ticket payment + reconciliation export
import requests

requests.post('https://gateway.pewang.company/api/mpesa/stk-push', json={
    'phone': '254712345678',
    'amountKes': 1500,
    'accountReference': 'TICKET-A',
    'transactionDesc': 'EventTicket',
    'clientId': 'events_bot'
}, headers={
    'x-api-key': 'sk_live_...',
    'Idempotency-Key': 'ticket-001-unique'
})

report = requests.get(
    'https://gateway.pewang.company/api/mpesa/transactions?from=2026-04-01T00:00:00.000Z&to=2026-04-30T23:59:59.999Z&status=success',
    headers={'x-api-key': 'sk_live_...'}
).json()

🎁 Birthday / Group Gifting

// WhatsApp command examples for gift pooling
// PAY500BIRTHDAY-JANE
// LIPA1000BIRTHDAY-JANE
// PAY250GIFT-KEVIN

// Use accountReference to group all contributions, then pull summary:
// GET /api/mpesa/reconciliation/summary?from=...&to=...

πŸ“’ WhatsApp Marketing Guide

Turn WhatsApp into your most powerful sales channel. The Pewang Gateway gives you everything you need to reach customers instantly, promote products, send offers, and close sales β€” all through WhatsApp.

πŸ’° Why WhatsApp Marketing?
  • 98% open rate β€” compared to 20% for email
  • 45% response rate β€” 10x higher than SMS
  • Instant delivery β€” messages arrive in seconds
  • Rich media β€” send images, videos, PDFs, catalogs
  • Personal touch β€” customers already use WhatsApp daily

πŸ‘Ÿ Use Case: Shoe Shop β€” Product Launch Campaign

Send a product launch blast to your entire customer list with images and a direct order link:

// Node.js β€” Product Launch Broadcast Campaign
const axios = require('axios');

const API_KEY = 'your_api_key';
const BASE_URL = 'https://gateway.pewang.company';
const CLIENT_ID = 'your_client_id';

// Customer list from your database
const customers = [
    { phone: '254712345678', name: 'John' },
    { phone: '254713456789', name: 'Mary' },
    { phone: '254714567890', name: 'Peter' },
    // ... up to 500+ contacts
];

async function sendProductLaunch() {
    for (const customer of customers) {
        try {
            // Send product image with personalized message
            await axios.post(`${BASE_URL}/api/messages/send`, {
                clientId: CLIENT_ID,
                to: customer.phone,
                type: 'image',
                imageUrl: 'https://yourshop.com/new-jordans.jpg',
                caption: `πŸ‘Ÿ *Hey ${customer.name}!* \n\n` +
                    `*NEW DROP!* Jordan 1 Retro High \ud83d\udd25\n\n` +
                    `\u2b50 Limited Edition\n` +
                    `\ud83d\udcb0 KES 15,000 (was KES 18,500)\n` +
                    `\ud83d\udce6 Free delivery in Nairobi\n\n` +
                    `Reply *ORDER* to grab yours before they sell out!`
            }, {
                headers: { 'x-api-key': API_KEY }
            });

            console.log(`\u2705 Sent to ${customer.name}`);
            // 2-second delay to avoid rate limiting
            await new Promise(r => setTimeout(r, 2000));
        } catch (err) {
            console.error(`\u274c Failed for ${customer.name}:`, err.message);
        }
    }
}

sendProductLaunch();

πŸ”₯ Use Case: LPG Gas β€” Refill Reminder Campaign

Automate refill reminders to customers who bought gas 3-4 weeks ago:

# Python β€” Automated Gas Refill Reminders
import requests
import time
from datetime import datetime, timedelta

API_KEY = "your_api_key"
BASE_URL = "https://gateway.pewang.company"
CLIENT_ID = "your_client_id"

# Customers who last ordered 25+ days ago
customers_due = [
    {"phone": "254712345678", "name": "Mama Njeri", "gas_size": "13kg"},
    {"phone": "254713456789", "name": "Mr. Ochieng", "gas_size": "6kg"},
]

def send_refill_reminders():
    for customer in customers_due:
        message = (
            f"πŸ”₯ *Hi {customer['name']}!*\n\n"
            f"It's been about a month since your last " 
            f"{customer['gas_size']} gas refill.\n\n"
            f"\u26fd *Time for a top-up?*\n\n"
            f"\u2713 {customer['gas_size']} Refill \u2014 "
            f"{('KES 1,800' if customer['gas_size'] == '6kg' else 'KES 3,200')}\n"
            f"\u2713 Free Delivery\n"
            f"\u2713 30-min delivery\n\n"
            f"Reply *YES* to order now!"
        )
        
        response = requests.post(
            f"{BASE_URL}/api/messages/send",
            json={
                "clientId": CLIENT_ID,
                "to": customer["phone"],
                "message": message
            },
            headers={"x-api-key": API_KEY}
        )
        print(f"\u2705 Reminder sent to {customer['name']}")
        time.sleep(2)

send_refill_reminders()

πŸ“¦ Use Case: E-Commerce β€” Flash Sale Broadcast

Run a flash sale campaign using the Broadcast API to reach all your customers at once:

// PHP β€” Flash Sale Broadcast with Product Image
<?php
$apiKey = 'your_api_key';
$baseUrl = 'https://gateway.pewang.company';
$clientId = 'your_client_id';

// Load customer list from database
$customers = [
    ['phone' => '254712345678', 'name' => 'Customer 1'],
    ['phone' => '254713456789', 'name' => 'Customer 2'],
];

foreach ($customers as $customer) {
    $data = [
        'clientId' => $clientId,
        'to' => $customer['phone'],
        'type' => 'image',
        'imageUrl' => 'https://yourshop.com/flash-sale-banner.jpg',
        'caption' => "πŸ”₯ *FLASH SALE β€” 3 HOURS ONLY!* πŸ”₯\n\n"
            . "Hi {$customer['name']}!\n\n"
            . "⭐ *50% OFF* all items\n"
            . "🚚 Free delivery countrywide\n"
            . "⏰ Sale ends at 9 PM tonight!\n\n"
            . "Reply *SHOP* to see products"
    ];

    $ch = curl_init("{$baseUrl}/api/messages/send");
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        "x-api-key: {$apiKey}"
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $result = curl_exec($ch);
    curl_close($ch);

    echo "\u2705 Sent to {$customer['name']}\n";
    sleep(2); // Rate limiting
}
?>

πŸ“… Use Case: Scheduled Promotions

Schedule promotional messages to go out at the best time using our Broadcast API:

// Node.js β€” Schedule a promotional broadcast for tomorrow 9 AM
const axios = require('axios');

async function schedulePromotion() {
    const tomorrow9am = new Date();
    tomorrow9am.setDate(tomorrow9am.getDate() + 1);
    tomorrow9am.setHours(9, 0, 0, 0);

    const response = await axios.post('https://gateway.pewang.company/api/broadcast', {
        clientId: 'your_client_id',
        recipients: [
            '254712345678', '254713456789',
            '254714567890', '254715678901'
        ],
        message: '\ud83c\udf89 *WEEKEND SPECIAL!* \ud83c\udf89\n\n'
            + 'Buy any 2 items and get the 3rd *FREE!*\n\n'
            + '\ud83d\uded2 Shop now: yourshop.com\n'
            + '\ud83d\udcde WhatsApp us to order\n\n'
            + 'Offer valid this weekend only! \u23f0',
        scheduledAt: tomorrow9am.toISOString()
    }, {
        headers: {
            'Authorization': 'Bearer your_token',
            'Content-Type': 'application/json'
        }
    });

    console.log('\u2705 Campaign scheduled!', response.data);
}

schedulePromotion();

πŸ“£ Marketing Campaign Strategies

Here are proven marketing strategies you can run through the Pewang Gateway:

Campaign Type Best For How to Run
πŸ”₯ Flash Sales Shoes, clothing, electronics Broadcast API with product image + limited-time offer
πŸ”” Refill Reminders LPG gas, water, subscriptions Scheduled messages based on last purchase date
🎁 Loyalty Programs Any repeat-purchase business Bot flow with VIP club join + automated rewards
πŸ“¦ New Product Launch Any product business Image broadcast with early-bird pricing
πŸ† Customer Reviews Hotels, restaurants, services Automated follow-up message after service delivery
πŸ“„ Catalogs & Price Lists Wholesalers, distributors Send PDF documents via the media API
πŸŽ₯ Video Demos Tech products, beauty, food Send product demo videos via the media API

πŸ€– Automated Marketing Bot

The most powerful feature: set up a bot that turns every incoming WhatsApp message into a sales opportunity.

When a customer texts your number, the bot can automatically:

  • Show your product catalog with prices
  • Share product images and videos
  • Collect orders (item + size + delivery location)
  • Send payment instructions (M-Pesa, bank details)
  • Offer exclusive discounts to new customers
  • Run 24/7 β€” never miss a sale!
πŸš€ Get Started in 3 Steps:
  1. Log in to your Console Dashboard
  2. Go to Developer β†’ Bot Flow Builder β†’ click Templates β†’ choose Shoe Shop, LPG Gas, or Product Marketing
  3. Customize texts with your prices and products β†’ click Save & Deploy

Your marketing bot is live in under 5 minutes! πŸŽ‰

πŸ“Š Best Practices

  • Personalize messages β€” Use customer names for 2x higher response rates
  • Include images β€” Visual product photos get 3x more engagement
  • Time it right β€” Send between 9 AM-12 PM or 5 PM-8 PM for best results
  • Add urgency β€” "Ends tonight!", "Only 5 left!" drive faster action
  • Use WhatsApp formatting β€” *bold* for emphasis, _italic_ for subtlety
  • Include a clear CTA β€” "Reply ORDER", "Reply YES", "Click this link"
  • Don't spam β€” Max 2-3 promotional messages per week per customer
  • Segment your list β€” Send relevant offers to the right audience

πŸ›οΈ WhatsApp E-Commerce Shop

Turn your WhatsApp number into a full online store. Customers can browse products, add to cart, receive invoices, pay via M-Pesa, and track their orders β€” all within a WhatsApp chat.

πŸš€ Complete E-Commerce Flow on WhatsApp:
  1. πŸ‘€ Customer texts your WhatsApp number
  2. πŸ€– Bot shows welcome + product categories
  3. πŸ›’ Customer browses β†’ selects items β†’ adds to cart
  4. πŸ“ Bot generates personalized invoice with totals
  5. πŸ’³ Customer pays via M-Pesa (Paybill/Till)
  6. βœ… You confirm payment β†’ dispatch order
  7. 🚚 Customer tracks delivery via WhatsApp

πŸ’» Full E-Commerce Bot Integration (Node.js)

Here's how a developer builds a complete WhatsApp shop backend that handles orders and generates invoices:

// Node.js β€” WhatsApp E-Commerce Order Handler
const axios = require('axios');

const API_KEY = 'your_api_key';
const BASE_URL = 'https://gateway.pewang.company';
const CLIENT_ID = 'your_client_id';

// In-memory cart (use database in production)
const carts = {};

const products = {
    '1': { name: 'Floral Maxi Dress', price: 2500 },
    '2': { name: 'Bodycon Dress', price: 1800 },
    '3': { name: 'Nike Air Max', price: 8500 },
    '4': { name: '13kg Gas Refill', price: 3200 },
};

// Handle incoming WhatsApp messages via webhook
function handleOrder(phone, message) {
    const msg = message.trim().toUpperCase();

    // ORDER command: ORDER 1 M 2
    if (msg.startsWith('ORDER')) {
        const parts = msg.split(' ');
        const itemId = parts[1];
        const size = parts[2] || 'N/A';
        const qty = parseInt(parts[3]) || 1;
        const product = products[itemId];

        if (!product) return sendMsg(phone, '\u274c Item not found. Check catalog.');

        if (!carts[phone]) carts[phone] = [];
        carts[phone].push({ ...product, size, qty });

        sendMsg(phone,
            `\u2705 *Added to cart!*\n\n` +
            `${product.name} (${size}) x${qty}\n` +
            `Subtotal: KES ${product.price * qty}\n\n` +
            `Reply *CART* to view cart\nReply *INVOICE* for total`
        );
    }

    // INVOICE command: Generate formatted invoice
    if (msg === 'INVOICE' || msg === 'PAY') {
        const cart = carts[phone];
        if (!cart || cart.length === 0)
            return sendMsg(phone, '\ud83d\uded2 Your cart is empty!');

        let total = 0;
        let invoice = '\ud83d\udcdd *INVOICE*\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n';

        cart.forEach(item => {
            const sub = item.price * item.qty;
            invoice += `\u2022 ${item.name} (${item.size}) x${item.qty} \u2014 KES ${sub.toLocaleString()}\n`;
            total += sub;
        });

        const delivery = 200;
        invoice += `\n\ud83d\ude9a Delivery \u2014 KES ${delivery}`;
        invoice += `\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`;
        invoice += `\n*TOTAL: KES ${(total + delivery).toLocaleString()}*`;
        invoice += `\n\n\ud83d\udcb3 *Pay via M-Pesa:*`;
        invoice += `\nPaybill: XXXXXX`;
        invoice += `\nAccount: ${phone}`;
        invoice += `\n\n\u2705 Send M-Pesa screenshot to confirm!`;

        sendMsg(phone, invoice);
    }
}

async function sendMsg(to, message) {
    await axios.post(`${BASE_URL}/api/messages/send`, {
        clientId: CLIENT_ID, to, message
    }, { headers: { 'x-api-key': API_KEY } });
}

πŸ’³ M-Pesa Payment Confirmation

After a customer pays, your system can listen for M-Pesa callbacks and automatically send confirmation:

// Auto-confirm payment and send dispatch notification
async function confirmPayment(phone, amount, mpesaCode) {
    const message =
        `\u2705 *PAYMENT CONFIRMED!*\n\n` +
        `Amount: KES ${amount}\n` +
        `M-Pesa Code: ${mpesaCode}\n\n` +
        `\ud83d\udce6 Your order is being prepared!\n` +
        `\ud83d\ude9a Estimated delivery: 2-4 hours\n\n` +
        `Reply *TRACK* for live updates.`;

    await axios.post(`${BASE_URL}/api/messages/send`, {
        clientId: CLIENT_ID, to: phone, message
    }, { headers: { 'x-api-key': API_KEY } });
}

πŸŽ₯ Social Media & Content Creators

Use the Pewang Gateway to build a powerful WhatsApp presence for your social media brand. Here's what works, what's possible, and what's not β€” honestly.

βœ… What WORKS Perfectly

Feature How It Works Use Case
πŸ”΄ TikTok Live Blasts Broadcast API sends "Going live NOW!" + TikTok link to your list Boost live viewership 5x
πŸ“Ί YouTube Premiere Alerts Schedule broadcast for premiere time with video thumbnail Drive first-hour views
πŸ›οΈ Merch Drops Send product images + order directly in WhatsApp Sell merch without a website
πŸŽ‰ Giveaway Announcements Broadcast giveaway rules + form link to fans High-engagement contests
πŸ“± Fan VIP Groups Bot auto-responds with exclusive content + schedules Build loyal fan community

⚠️ What's NOT Possible (Being Honest)

Feature Why Not Alternative
Post to Facebook from WhatsApp Facebook requires Graph API + OAuth (separate platform) Send FB post links via WhatsApp to drive engagement
Auto-detect TikTok/IG lives TikTok & Instagram don't expose live-start webhooks One-click broadcast from Console when you go live
Post Instagram Stories IG Stories API is restricted to business partners Share IG story links via WhatsApp broadcast
Cross-post between platforms Each platform has separate APIs & auth Use WhatsApp as the notification hub that links to all platforms

πŸ”΄ TikTok Live Blast β€” Code Example

When you go live on TikTok, instantly notify all your WhatsApp followers:

// Node.js β€” TikTok Live Notification Blast
const axios = require('axios');

const API_KEY = 'your_api_key';
const BASE_URL = 'https://gateway.pewang.company';
const CLIENT_ID = 'your_client_id';

// Your WhatsApp subscriber list
const subscribers = [
    '254712345678', '254713456789',
    '254714567890', '254715678901',
    // ... your fans
];

async function blastLiveNotification(platform, liveLink) {
    const message =
        `\ud83d\udd34 *I'M GOING LIVE NOW!* \ud83d\udd34\n\n` +
        `\ud83c\udfa4 Live on *${platform}*!\n\n` +
        `\ud83d\udd17 Join here: ${liveLink}\n\n` +
        `Don't miss it! \ud83d\udd25`;

    let sent = 0;
    for (const phone of subscribers) {
        try {
            await axios.post(`${BASE_URL}/api/messages/send`, {
                clientId: CLIENT_ID, to: phone, message
            }, { headers: { 'x-api-key': API_KEY } });
            sent++;
            await new Promise(r => setTimeout(r, 1500));
        } catch (e) {
            console.error(`Failed: ${phone}`);
        }
    }
    console.log(`\u2705 Notified ${sent}/${subscribers.length} fans!`);
}

// When you go live, run this:
blastLiveNotification(
    'TikTok',
    'https://www.tiktok.com/@yourhandle/live'
);

πŸ’‘ The Smart Approach: WhatsApp as Your Marketing Hub

Instead of trying to post TO social media from WhatsApp (which isn't technically possible), use WhatsApp as your central notification hub that drives traffic to all your platforms:

  • πŸ“± Going live on TikTok? β†’ Blast your WhatsApp list with the live link
  • πŸ“Ί New YouTube video? β†’ Send thumbnail + link via broadcast
  • πŸ“Έ Instagram post? β†’ Share the post link to drive likes/comments
  • πŸ“ Facebook event? β†’ Send event link + RSVP to your WhatsApp contacts
  • πŸ›οΈ Selling merch? β†’ Send product images + take orders directly in WhatsApp

WhatsApp has 98% open rate vs. 2% on social media algorithms. Your message is guaranteed to be seen!