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.com/api

Authentication

All API requests must include your API Key in the x-api-key header.

You can generate and manage your API keys from the Developer Console dashboard.

curl -X POST https://gateway.pewang.com/api/sendSMS \
  -H "x-api-key: YOUR_API_KEY" \
  ...

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 a WhatsApp message via your connected WhatsApp client.

POST /api/sendWhatsApp
GET /api/sendWhatsApp

Parameters

Parameter Type Required Description
to string Yes Recipient phone number (international format without +)
msg string Yes Message content (supports Markdown)
client string Yes Client ID of the sender (from your dashboard)

Example

curl "https://gateway.pewang.com/api/sendWhatsApp?to=254712345678&msg=Hello&client=my_shop" \
  -H "x-api-key: YOUR_API_KEY"

🔐 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 and incoming messages.

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

Webhook Events

Message Sent

Triggered when a message is successfully sent/queued.

{
  "event": "message.sent",
  "timestamp": "2023-10-27T10:00:00Z",
  "data": {
    "id": "...",
    "to": "2547...",
    "type": "SMS",
    "status": "QUEUED"
  }
}

Message Received

Triggered when a new SMS is received on the gateway.

{
  "event": "message.received",
  "timestamp": "...",
  "data": {
    "type": "SMS",
    "from": "2547...",
    "body": "Hello world"
  }
}