API Documentation
Welcome to the Pewang Gateway API. Build powerful messaging experiences and secure authentication flows using your own devices.
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:
- Go to console.pewang.company
- Navigate to WhatsApp section
- 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.
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.
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.
How It Works
SIMCall Verify™ uses a challenge-response protocol based on call duration timing:
- Challenge Created - Server generates random target duration (3-25s)
- Call Placed - User receives call from verification system
- User Answers & Holds - Must hold call for exact duration
- User Hangs Up - Timing measured server-side
- 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
| 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
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.
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
etafield 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
successfield - 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"
}
}