WA-Connect API

Self-hosted WhatsApp Web API for cPanel

Send messages, manage sessions, and integrate WhatsApp into your applications

Overview

Base URL
https://your-domain.com/api/v1
Response Format

All API responses follow this format:

// Success Response
{
    "success": true,
    "data": { ... },
    "message": "Optional success message"
}

// Error Response
{
    "success": false,
    "message": "Error description",
    "error": "Detailed error message"
}

Authentication

All API requests (except health check) require the X-API-Key header:

X-API-Key: your-api-key-here
API keys can be created from the Settings > API Keys page in the dashboard.
Example Request
curl -X POST https://your-domain.com/api/v1/messages/send-text \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key-here" \
  -d '{"to": "968XXXXXXXX", "message": "Hello!"}'

Choosing the send channel / اختيار قناة الإرسال

Each container may support two independent send channels:

  • Baileys — WhatsApp Web session(s), used via /api/v1/messages/send-*.
  • Cloud API — Meta's official WhatsApp Business API, used via /api/v1/cloud-api/*.

Every API key can be permanently bound to one specific channel from the dashboard (Settings > API Keys > Send channel). A bound key is rejected on the other channel's endpoints with CHANNEL_MISMATCH — so external code cannot accidentally send through the wrong account. Keys with binding Auto follow per-request headers.

Resolution order (Baileys endpoints)
  1. X-Session-Key header — per-request override.
  2. API key bound to baileys:<id>.
  3. Legacy api_keys.session_id.
  4. Primary session — default fallback.
Resolution order (Cloud API endpoints)
  1. X-Cloud-Account-Id header — per-request override.
  2. API key bound to cloud_api:<id>.
  3. First active Cloud API account — single-account convenience.
Example — send from a specific Baileys session
curl -X POST https://your-domain.com/api/v1/messages/send-text \
  -H "X-API-Key: your_api_key" \
  -H "X-Session-Key: sales" \
  -H "Content-Type: application/json" \
  -d '{"to":"96812345678","message":"Hello"}'
Example — send from a specific Cloud API account
curl -X POST https://your-domain.com/api/v1/cloud-api/send-text \
  -H "X-API-Key: your_api_key" \
  -H "X-Cloud-Account-Id: 2" \
  -H "Content-Type: application/json" \
  -d '{"to":"96812345678","message":"Hello"}'
Binding an API key to a channel from the dashboard is the persistent alternative to sending routing headers on every request. Create one bound key per channel instead of a single key that guesses.

On legacy single-session containers the X-Session-Key header is silently ignored and every Baileys call routes to the one session — the reference is safe even before multi-session is enabled.

Session Management

GET /api/v1/health No Auth Required

Check if the API server is running.

Response
{
    "success": true,
    "message": "WA-Connect API is running",
    "timestamp": "2026-02-27T10:00:00.000Z"
}
GET /api/v1/session/status

Get current WhatsApp session status.

Response
{
    "success": true,
    "data": {
        "status": "connected",
        "user": {
            "id": "968XXXXXXXX@s.whatsapp.net",
            "name": "Your Name"
        },
        "connected_at": "2026-02-27T10:00:00.000Z"
    }
}
POST /api/v1/session/connect

Start WhatsApp connection. Returns QR code if not authenticated.

Response (QR Code needed)
{
    "success": true,
    "data": {
        "status": "qr_ready",
        "qr": "2@ABC123...base64-qr-data..."
    }
}
Response (Already authenticated)
{
    "success": true,
    "data": {
        "status": "connecting",
        "message": "Reconnecting with saved credentials"
    }
}
POST /api/v1/session/disconnect

Disconnect WhatsApp session (keeps credentials for reconnection).

Response
{
    "success": true,
    "message": "Disconnected successfully"
}
POST /api/v1/session/logout

Logout from WhatsApp and clear all session data.

Response
{
    "success": true,
    "message": "Logged out successfully"
}

Messages

POST /api/v1/messages/send-text

Send a text message to a WhatsApp number.

Request Body
{
    "to": "968XXXXXXXX",
    "message": "Hello from WA-Connect!"
}
Response
{
    "success": true,
    "data": {
        "message_id": "3EB0XXXXX",
        "to": "968XXXXXXXX@s.whatsapp.net",
        "status": "sent",
        "timestamp": "2026-02-27T10:00:00.000Z"
    }
}
POST /api/v1/messages/send-image

Send an image with optional caption.

Request Body
{
    "to": "968XXXXXXXX",
    "imageUrl": "https://example.com/image.jpg",
    "caption": "Check this out!"  // optional
}
Response
{
    "success": true,
    "data": {
        "message_id": "3EB0XXXXX",
        "to": "968XXXXXXXX@s.whatsapp.net",
        "status": "sent"
    }
}
POST /api/v1/messages/send-document

Send a document/file.

Request Body
{
    "to": "968XXXXXXXX",
    "documentUrl": "https://example.com/document.pdf",
    "mimetype": "application/pdf",
    "fileName": "Invoice.pdf",
    "caption": "Here is your invoice"  // optional
}
Response
{
    "success": true,
    "data": {
        "message_id": "3EB0XXXXX",
        "to": "968XXXXXXXX@s.whatsapp.net",
        "status": "sent"
    }
}
POST /api/v1/messages/send-video

Send a video with optional caption.

Request Body
{
    "to": "968XXXXXXXX",
    "videoUrl": "https://example.com/video.mp4",
    "caption": "Watch this!"  // optional
}
Response
{
    "success": true,
    "data": {
        "message_id": "3EB0XXXXX",
        "to": "968XXXXXXXX@s.whatsapp.net",
        "status": "sent"
    }
}
POST /api/v1/messages/send-audio

Send an audio file or voice note.

Request Body
{
    "to": "968XXXXXXXX",
    "audioUrl": "https://example.com/audio.mp3",
    "ptt": false  // set to true for voice note
}
Response
{
    "success": true,
    "data": {
        "message_id": "3EB0XXXXX",
        "to": "968XXXXXXXX@s.whatsapp.net",
        "status": "sent"
    }
}
POST /api/v1/messages/send-location

Send a location pin.

Request Body
{
    "to": "968XXXXXXXX",
    "latitude": 23.5880,
    "longitude": 58.3829,
    "name": "Muscat, Oman",       // optional
    "address": "Muscat, Oman"     // optional
}
Response
{
    "success": true,
    "data": {
        "message_id": "3EB0XXXXX",
        "to": "968XXXXXXXX@s.whatsapp.net",
        "status": "sent"
    }
}
POST /api/v1/messages/send-contact

Send a contact card (vCard).

Request Body
{
    "to": "968XXXXXXXX",
    "contactName": "John Doe",
    "contactPhone": "+96899999999"
}
Response
{
    "success": true,
    "data": {
        "message_id": "3EB0XXXXX",
        "to": "968XXXXXXXX@s.whatsapp.net",
        "status": "sent"
    }
}
POST /api/v1/messages/send-bulk

Send the same message to multiple recipients.

Request Body
{
    "recipients": ["968XXXXXXXX", "968YYYYYYYY", "968ZZZZZZZZ"],
    "message": "Hello everyone!",
    "delay": 1000  // optional, delay between messages in ms (default: 1000)
}
Response
{
    "success": true,
    "data": {
        "total": 3,
        "successful": 3,
        "failed": 0,
        "results": [
            { "to": "968XXXXXXXX", "success": true, "message_id": "3EB0XXX" },
            { "to": "968YYYYYYYY", "success": true, "message_id": "3EB0YYY" },
            { "to": "968ZZZZZZZZ", "success": true, "message_id": "3EB0ZZZ" }
        ]
    }
}

Message Log

GET /api/v1/messages/log

Get paginated message history.

Query Parameters
ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger20Items per page
directionstringallFilter: inbound, outbound, or all
Response
{
    "success": true,
    "data": {
        "messages": [
            {
                "id": 1,
                "message_id": "3EB0XXXXX",
                "remote_jid": "968XXXXXXXX@s.whatsapp.net",
                "remote_name": "John Doe",
                "direction": "outbound",
                "message_type": "text",
                "content": "Hello!",
                "status": "sent",
                "created_at": "2026-02-27T10:00:00.000Z"
            }
        ],
        "pagination": {
            "page": 1,
            "limit": 20,
            "total": 150,
            "totalPages": 8
        }
    }
}
GET /api/v1/messages/:id

Get a single message by ID or message_id.

Response
{
    "success": true,
    "data": {
        "id": 1,
        "message_id": "3EB0XXXXX",
        "remote_jid": "968XXXXXXXX@s.whatsapp.net",
        "remote_name": "John Doe",
        "direction": "outbound",
        "message_type": "text",
        "content": "Hello!",
        "status": "sent",
        "created_at": "2026-02-27T10:00:00.000Z"
    }
}

Cloud API Templates

Discover approved templates on Meta's WhatsApp Cloud API and send them with numbered {{1}} {{2}} parameters. Requires a configured Cloud API account. Which account is used follows the resolution order in Choosing the send channel — the X-Cloud-Account-Id header is the per-request override.

GET /api/v1/cloud-api/templates

List approved templates for the account, with optional server-side filters.

Query Parameters
ParameterTypeDefaultDescription
languagestring-Filter by exact language code (e.g. ar, en_US)
categorystring-AUTHENTICATION | MARKETING | UTILITY
statusstringAPPROVEDAPPROVED | PENDING | REJECTED | ALL
searchstring-Case-insensitive substring match on name
limitinteger50Page size (1..200)
Response
{
    "success": true,
    "data": {
        "count": 2,
        "templates": [
            {
                "name": "otp_login",
                "language": "ar",
                "category": "AUTHENTICATION",
                "status": "APPROVED",
                "body": "رمز التحقق: {{1}}",
                "param_count": 1,
                "param_placeholders": ["{{1}}"],
                "header": null,
                "footer": null,
                "buttons": []
            }
        ]
    }
}

Discovery requires the Access Token to have the whatsapp_business_management permission. Results are cached for 60 seconds.

GET /api/v1/cloud-api/templates/:name

Get one template's full detail — useful for extracting placeholders before sending.

Query Parameters
ParameterTypeRequiredDescription
languagestringyesLanguage code — same template may exist in multiple languages
Response
{
    "success": true,
    "data": {
        "name": "otp_login",
        "language": "ar",
        "category": "AUTHENTICATION",
        "status": "APPROVED",
        "body": "رمز التحقق: {{1}}",
        "param_count": 1,
        "param_placeholders": ["{{1}}"],
        "header": { "type": "TEXT", "text": "تحقق من الحساب" },
        "footer": null,
        "buttons": []
    }
}
POST /api/v1/cloud-api/templates/:name/send

Send a template. Accepts params as an array (positional) OR a numbered object.

Body — array form
{
    "to": "96812345678",
    "language": "ar",
    "params": ["123456"]
}
Body — numbered object form
{
    "to": "96812345678",
    "language": "ar",
    "params": {
        "1": "Ahmed",
        "2": "1234",
        "3": "150 SAR"
    }
}

The object form's keys are parsed as integers and sorted, so {"2":"b","1":"a"} is equivalent to ["a","b"]. Missing intermediate numbers become empty strings.

Response
{
    "success": true,
    "data": {
        "messaging_product": "whatsapp",
        "messages": [{ "id": "wamid.HB..." }],
        "sent_at": "2026-07-23T10:15:00.000Z"
    }
}

Error Handling

HTTP Status Codes
CodeDescription
200Success
400Bad Request - Missing or invalid parameters
401Unauthorized - Invalid or missing API key
403Forbidden - API key lacks required permission
404Not Found - Resource not found
429Too Many Requests - Rate limit exceeded
500Internal Server Error
503Service Unavailable - WhatsApp not connected
Application error codes
CodeWhen
CHANNEL_MISMATCHAPI key is bound to one channel but the endpoint belongs to the other (e.g. a cloud_api:-bound key hitting /messages/send-*).
ACCOUNT_NOT_FOUNDX-Cloud-Account-Id header or bound account id no longer exists in cloud_api_accounts.
NO_ACCOUNTSCloud API endpoint called on a tenant without any configured account.
FEATURE_OFFCloud API is not enabled on this container.
Error Response Example
{
    "success": false,
    "message": "WhatsApp is not connected",
    "error": "Service unavailable"
}

Rate Limits

API requests are rate-limited to prevent abuse:

Endpoint TypeLimit
General API100 requests per 15 minutes
Message Sending30 messages per minute
API Key Daily LimitConfigurable per key (default: 1000)
When rate limit is exceeded, you'll receive a 429 Too Many Requests response. Wait before retrying.

Webhooks

Overview

Webhooks allow your application to receive real-time notifications when events occur in WA-Connect. When an event is triggered, WA-Connect sends an HTTP POST request to your configured URL.

Event Types
EventDescription
message.receivedA new message was received
message.sentA message was sent successfully
message.statusMessage delivery status updated (delivered, read)
connection.connectedWhatsApp session connected
connection.disconnectedWhatsApp session disconnected
connection.qr_readyQR code is ready for scanning
call.incomingIncoming call received
call.rejectedIncoming call was rejected
Webhook Payload Format

All webhook requests are sent as HTTP POST with JSON body:

{
    "event": "message.received",
    "timestamp": "2026-02-27T10:00:00.000Z",
    "data": {
        // Event-specific data
    }
}
Request Headers
HeaderDescription
Content-Typeapplication/json
User-AgentWA-Connect-Webhook/1.0
X-Webhook-EventThe event type
X-Webhook-TimestampEvent timestamp (ISO 8601)
X-Webhook-SignatureHMAC-SHA256 signature (if secret configured)
X-Webhook-Signature-256sha256=SIGNATURE (GitHub-style format)
Signature Verification

If you configure a secret for your webhook, WA-Connect signs every request using HMAC-SHA256. You should verify this signature to ensure the request is authentic.

Verification Example (Node.js)
const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
    const expected = crypto
        .createHmac('sha256', secret)
        .update(JSON.stringify(payload))
        .digest('hex');

    return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expected)
    );
}

// In your webhook handler:
app.post('/webhook', (req, res) => {
    const signature = req.headers['x-webhook-signature'];
    const isValid = verifySignature(req.body, signature, 'your-secret');

    if (!isValid) {
        return res.status(401).send('Invalid signature');
    }

    // Process the webhook...
    res.status(200).send('OK');
});
EVENT message.received

Triggered when a new message is received.

Payload
{
    "event": "message.received",
    "timestamp": "2026-02-27T10:00:00.000Z",
    "data": {
        "message_id": "3EB0XXXXX",
        "from": "968XXXXXXXX@s.whatsapp.net",
        "from_name": "John Doe",
        "type": "text",
        "content": "Hello!",
        "media_url": null,
        "is_group": false,
        "timestamp": "2026-02-27T10:00:00.000Z"
    }
}
EVENT message.sent

Triggered when a message is sent successfully.

Payload
{
    "event": "message.sent",
    "timestamp": "2026-02-27T10:00:00.000Z",
    "data": {
        "message_id": "3EB0XXXXX",
        "to": "968XXXXXXXX@s.whatsapp.net",
        "type": "text",
        "content": "Hello!",
        "status": "sent"
    }
}
EVENT message.status

Triggered when message delivery status is updated.

Payload
{
    "event": "message.status",
    "timestamp": "2026-02-27T10:00:00.000Z",
    "data": {
        "message_id": "3EB0XXXXX",
        "remote_jid": "968XXXXXXXX@s.whatsapp.net",
        "status": "delivered"  // "sent", "delivered", "read", "failed"
    }
}
EVENT connection.connected

Triggered when WhatsApp session is connected.

Payload
{
    "event": "connection.connected",
    "timestamp": "2026-02-27T10:00:00.000Z",
    "data": {
        "status": "connected",
        "phone_number": "968XXXXXXXX",
        "wa_name": "Your Name"
    }
}
EVENT connection.disconnected

Triggered when WhatsApp session is disconnected.

Payload
{
    "event": "connection.disconnected",
    "timestamp": "2026-02-27T10:00:00.000Z",
    "data": {
        "status": "disconnected",
        "reason": "logout"
    }
}
EVENT call.incoming

Triggered when an incoming call is received.

Payload
{
    "event": "call.incoming",
    "timestamp": "2026-02-27T10:00:00.000Z",
    "data": {
        "from": "968XXXXXXXX@s.whatsapp.net",
        "call_id": "CALL_ID_HERE",
        "is_video": false
    }
}
Retry Logic

If your webhook endpoint returns a non-2xx status code or times out, WA-Connect will retry the delivery:

  • Default retry attempts: 3 (configurable per webhook)
  • Retry delay: 30 seconds between attempts
  • Timeout: 30 seconds per request
Your endpoint should respond with HTTP 200 within 30 seconds to acknowledge receipt.

Contacts

POST /api/v1/contacts/check

Check if a phone number is registered on WhatsApp.

Request Body
{
    "phone": "968XXXXXXXX"
}
Response (Number on WhatsApp)
{
    "success": true,
    "exists": true,
    "message": "Number is on WhatsApp",
    "contact": {
        "jid": "968XXXXXXXX@s.whatsapp.net",
        "phone": "968XXXXXXXX",
        "name": "John Doe"
    }
}
Response (Number NOT on WhatsApp)
{
    "success": true,
    "exists": false,
    "message": "Number is not on WhatsApp"
}
GET /api/v1/contacts

Get list of synced contacts.

Query Parameters
ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger50Items per page
searchstring-Search by name or phone
typestringallFilter: individual, group, or all
Response
{
    "success": true,
    "data": {
        "contacts": [
            {
                "id": 1,
                "jid": "968XXXXXXXX@s.whatsapp.net",
                "phone": "968XXXXXXXX",
                "push_name": "John Doe",
                "is_group": false,
                "last_message_at": "2026-02-27T10:00:00.000Z"
            }
        ],
        "pagination": {
            "page": 1,
            "limit": 50,
            "total": 150,
            "totalPages": 3
        }
    }
}
GET /api/v1/contacts/:jid

Get a single contact by JID.

Response
{
    "success": true,
    "data": {
        "id": 1,
        "jid": "968XXXXXXXX@s.whatsapp.net",
        "phone": "968XXXXXXXX",
        "push_name": "John Doe",
        "is_group": false,
        "message_count": 25,
        "last_message_at": "2026-02-27T10:00:00.000Z",
        "created_at": "2026-02-20T08:00:00.000Z"
    }
}
GET /api/v1/contacts/search

Search contacts by name or phone number.

Query Parameters
ParameterTypeDescription
qstringSearch query (minimum 2 characters)
Response
{
    "success": true,
    "data": {
        "contacts": [
            {
                "id": 1,
                "jid": "968XXXXXXXX@s.whatsapp.net",
                "phone": "968XXXXXXXX",
                "push_name": "John Doe"
            }
        ]
    }
}
GET /api/v1/contacts/export

Export all contacts as CSV file.

Response

Returns a CSV file download with columns: jid, phone, name, is_group, message_count, last_message_at

POST /api/v1/contacts/sync

Trigger a manual contact sync from WhatsApp. Note: Contacts are synced automatically when WhatsApp connects.

Response
{
    "success": true,
    "message": "Contacts are synced automatically when WhatsApp connects."
}
Group Contacts

Group chats are also stored as contacts with is_group: true. You can filter them using the type parameter:

GET /api/v1/contacts?type=group