P
PayPerWA

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/v1

Response 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_here

Generate 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.

PlanRate Limit
Default100 requests/minute per key
Custom (configurable per key)10 -- 1,000 requests/minute

Send Message

POST/api/v1/messages/send

Send 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"]
}
FieldTypeRequiredDescription
tostringYesPhone number with country code (e.g. 919876543210)
template_namestringYesName of your approved template
languagestringNoTemplate language code (default: "en")
variablesstring[]NoTemplate 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

GET/api/v1/contacts

List all contacts with optional pagination and search.

Permission: contacts:read

Query Parameters

ParamDefaultDescription
page1Page number
pageSize50Items 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"
POST/api/v1/contacts

Create 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"]}'
GET/api/v1/contacts/{id}

Get a single contact by ID with tags and groups.

PUT/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"]
}
DELETE/api/v1/contacts/{id}

Soft-delete a contact (can be restored by support).

Templates

GET/api/v1/templates

List your approved message templates. Filter by status or category.

Permission: templates:read

Query Parameters

ParamDefaultDescription
statusAPPROVEDDRAFT, 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

GET/api/v1/campaigns

List 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
  }
}
POST/api/v1/campaigns

Create 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

GET/api/v1/balance

Check 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

StatusDescription
sentMessage sent to WhatsApp servers
deliveredMessage delivered to recipient's device
readRecipient read the message
failedMessage 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"
}
CodeStatusDescription
400Bad RequestInvalid request body or missing required fields
401UnauthorizedInvalid, missing, expired, or deactivated API key
402Payment RequiredInsufficient wallet balance. Recharge at Dashboard → Billing
403ForbiddenAPI key lacks the required permission for this endpoint
404Not FoundResource not found (contact, template, or campaign)
409ConflictDuplicate resource (e.g. contact with same phone number)
429Too Many RequestsRate limit exceeded. Wait and retry.
500Server ErrorInternal 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.

PermissionGrants Access To
contacts:readGET /api/v1/contacts, GET /api/v1/contacts/:id
contacts:writePOST /api/v1/contacts, PUT /api/v1/contacts/:id, DELETE /api/v1/contacts/:id
templates:readGET /api/v1/templates
messages:sendPOST /api/v1/messages/send
campaigns:readGET /api/v1/campaigns
campaigns:sendPOST /api/v1/campaigns (create + send)
balance:readGET /api/v1/balance

Ready to Integrate?

Sign up, grab your API key, and start sending WhatsApp messages in minutes.

Get Your API Key