Getting Started
The PayPerWA API lets you send WhatsApp messages, manage contacts, templates, and campaigns programmatically. All endpoints use the /api/v1 prefix and require API key authentication.
Base URL
https://payperwa.com/api/v1Response Format
All responses follow a consistent JSON format:
// Success
{
"success": true,
"data": { ... }
}
// Error
{
"success": false,
"error": "Human-readable error message"
}Authentication
Include your API key in the Authorization header as a Bearer token with every request.
Authorization: Bearer ppw_live_sk_your_key_hereGenerate your API key in Dashboard → Settings → API tab. You can create up to 5 keys per account. Keys are shown only once at creation time.
cURL Example
curl https://payperwa.com/api/v1/balance \
-H "Authorization: Bearer ppw_live_sk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"JavaScript (fetch)
const API_KEY = "ppw_live_sk_your_key_here";
const BASE = "https://payperwa.com/api/v1";
async function apiCall(path, options = {}) {
const res = await fetch(BASE + path, {
...options,
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
...options.headers,
},
});
return res.json();
}Rate Limits
Each API key has a configurable rate limit (default: 100 requests per minute). Exceeding this limit returns a 429 status code. Wait and retry after a few seconds.
| Plan | Rate Limit |
|---|---|
| Default | 100 requests/minute per key |
| Custom (configurable per key) | 10 -- 1,000 requests/minute |
Send Message
/api/v1/messages/sendSend a single WhatsApp message using an approved template. The cost (Meta fee + ₹0.20 platform fee) is deducted from your wallet balance.
Permission required: messages:send
Request Body
{
"to": "919876543210",
"template_name": "order_confirmation",
"language": "en",
"variables": ["Rahul", "ORD-4521", "₹1,299"]
}| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Phone number with country code (e.g. 919876543210) |
template_name | string | Yes | Name of your approved template |
language | string | No | Template language code (default: "en") |
variables | string[] | No | Template variable values in order |
Response
{
"success": true,
"data": {
"message_id": "wamid.HBgLMTIzNDU2Nzg5MA==",
"status": "sent",
"cost": {
"meta_fee": 0.86,
"platform_fee": 0.20,
"total": 1.06
}
}
}cURL Example
curl -X POST https://payperwa.com/api/v1/messages/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "919876543210",
"template_name": "order_confirmation",
"language": "en",
"variables": ["Rahul", "ORD-4521", "₹1,299"]
}'JavaScript (fetch)
const res = await fetch("https://payperwa.com/api/v1/messages/send", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "919876543210",
template_name: "order_confirmation",
language: "en",
variables: ["Rahul", "ORD-4521", "₹1,299"],
}),
});
const { success, data } = await res.json();
console.log(data.message_id);Contacts
/api/v1/contactsList all contacts with optional pagination and search.
Permission: contacts:read
Query Parameters
| Param | Default | Description |
|---|---|---|
page | 1 | Page number |
pageSize | 50 | Items per page (max 100) |
search | - | Search by name or phone |
// Response
{
"success": true,
"data": {
"contacts": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Rahul Sharma",
"phone": "+919876543210",
"email": "rahul@example.com",
"optedIn": true,
"tags": ["customer", "delhi"],
"createdAt": "2026-01-15T10:30:00.000Z"
}
],
"total": 1250,
"page": 1,
"pageSize": 50
}
}cURL Example
curl "https://payperwa.com/api/v1/contacts?page=1&pageSize=50" \
-H "Authorization: Bearer YOUR_API_KEY"/api/v1/contactsCreate a new contact. Tags are auto-created if they don't exist.
Permission: contacts:write
// Request
{
"name": "Priya Patel",
"phone": "9123456789",
"email": "priya@example.com",
"tags": ["lead", "mumbai"]
}
// Response (201)
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Priya Patel",
"phone": "+919123456789",
"email": "priya@example.com",
"optedIn": true,
"tags": ["lead", "mumbai"],
"createdAt": "2026-03-20T14:00:00.000Z"
}
}cURL Example
curl -X POST https://payperwa.com/api/v1/contacts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Priya Patel","phone":"9123456789","tags":["lead","mumbai"]}'/api/v1/contacts/{id}Get a single contact by ID with tags and groups.
/api/v1/contacts/{id}Update a contact. Send only the fields you want to change. Replacing tags replaces all existing tags.
// Request
{
"name": "Priya P.",
"tags": ["customer", "vip"]
}/api/v1/contacts/{id}Soft-delete a contact (can be restored by support).
Templates
/api/v1/templatesList your approved message templates. Filter by status or category.
Permission: templates:read
Query Parameters
| Param | Default | Description |
|---|---|---|
status | APPROVED | DRAFT, PENDING, APPROVED, REJECTED |
category | - | MARKETING, UTILITY, AUTHENTICATION |
// Response
{
"success": true,
"data": {
"templates": [
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"name": "order_confirmation",
"category": "UTILITY",
"language": "en",
"status": "APPROVED",
"body": "Hi {{1}}, your order {{2}} of {{3}} has been confirmed!",
"header": null,
"footer": "Thank you for shopping with us",
"buttons": null,
"createdAt": "2026-02-01T12:00:00.000Z"
}
]
}
}cURL Example
curl "https://payperwa.com/api/v1/templates?status=APPROVED" \
-H "Authorization: Bearer YOUR_API_KEY"Campaigns
/api/v1/campaignsList all campaigns with status and delivery stats.
Permission: campaigns:read
// Response
{
"success": true,
"data": {
"campaigns": [
{
"id": "550e8400-e29b-41d4-a716-446655440003",
"name": "Diwali Offer 2026",
"status": "COMPLETED",
"template": "promo_offer",
"templateCategory": "MARKETING",
"totalMessages": 5000,
"sentCount": 5000,
"deliveredCount": 4850,
"readCount": 3200,
"failedCount": 150,
"estimatedCost": "5300.00",
"actualCost": "5300.00",
"createdAt": "2026-03-10T09:00:00.000Z"
}
],
"total": 42,
"page": 1,
"pageSize": 20
}
}/api/v1/campaignsCreate and optionally send a campaign. Set send: true to immediately queue messages, or omit it to create as DRAFT.
Permission: campaigns:send
// Request
{
"name": "March Sale",
"templateId": "550e8400-e29b-41d4-a716-446655440002",
"groupIds": ["group-uuid-1"],
"variables": {
"1": "name",
"2": "20%",
"3": "MARCH20"
},
"send": true
}
// Response (201)
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440004",
"name": "March Sale",
"status": "SENDING",
"totalContacts": 5000,
"estimatedCost": {
"meta_fee": "4300.00",
"platform_fee": "1000.00",
"total": "5300.00"
},
"message": "Campaign queued. 5000 messages will be sent."
}
}cURL Example
curl -X POST https://payperwa.com/api/v1/campaigns \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "March Sale",
"templateId": "your-template-uuid",
"groupIds": ["your-group-uuid"],
"variables": { "1": "name", "2": "20%", "3": "MARCH20" },
"send": true
}'Wallet / Balance
/api/v1/balanceCheck your current wallet balance. All amounts in INR.
Permission: balance:read
// Response
{
"success": true,
"data": {
"balance": 4520.60,
"currency": "INR"
}
}cURL Example
curl https://payperwa.com/api/v1/balance \
-H "Authorization: Bearer YOUR_API_KEY"JavaScript (fetch)
const res = await fetch("https://payperwa.com/api/v1/balance", {
headers: { "Authorization": "Bearer YOUR_API_KEY" },
});
const { data } = await res.json();
console.log("Wallet balance:", data.balance);Webhooks
PayPerWA can send delivery status callbacks to your server whenever a message status changes (sent, delivered, read, or failed). Configure your webhook URL in Dashboard → Settings.
Webhook Payload
{
"event": "message.status",
"message_id": "wamid.HBgLMTIzNDU2Nzg5MA==",
"status": "delivered",
"timestamp": "2026-03-20T14:35:00Z",
"recipient": "919876543210",
"campaign_id": "camp_abc123"
}Status Values
| Status | Description |
|---|---|
sent | Message sent to WhatsApp servers |
delivered | Message delivered to recipient's device |
read | Recipient read the message |
failed | Message could not be delivered (wallet refunded) |
Verifying Webhooks
Each webhook request includes an X-PayPerWA-Signature header. Verify it using your webhook secret (available in Settings) with HMAC-SHA256.
const crypto = require("crypto");
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(JSON.stringify(body))
.digest("hex");
return signature === expected;
}Error Codes
All errors follow a consistent format:
{
"success": false,
"error": "Human-readable error message"
}| Code | Status | Description |
|---|---|---|
400 | Bad Request | Invalid request body or missing required fields |
401 | Unauthorized | Invalid, missing, expired, or deactivated API key |
402 | Payment Required | Insufficient wallet balance. Recharge at Dashboard → Billing |
403 | Forbidden | API key lacks the required permission for this endpoint |
404 | Not Found | Resource not found (contact, template, or campaign) |
409 | Conflict | Duplicate resource (e.g. contact with same phone number) |
429 | Too Many Requests | Rate limit exceeded. Wait and retry. |
500 | Server Error | Internal server error. Contact support if this persists. |
Permissions
Each API key can be scoped with specific permissions. New keys are created with all permissions by default. You can restrict them when creating or editing a key.
| Permission | Grants Access To |
|---|---|
contacts:read | GET /api/v1/contacts, GET /api/v1/contacts/:id |
contacts:write | POST /api/v1/contacts, PUT /api/v1/contacts/:id, DELETE /api/v1/contacts/:id |
templates:read | GET /api/v1/templates |
messages:send | POST /api/v1/messages/send |
campaigns:read | GET /api/v1/campaigns |
campaigns:send | POST /api/v1/campaigns (create + send) |
balance:read | GET /api/v1/balance |
Ready to Integrate?
Sign up, grab your API key, and start sending WhatsApp messages in minutes.
Get Your API Key