WApenda API

WhatsApp API reference

WApenda turns your own WhatsApp number into a REST API. Send messages and media, manage groups and contacts, and receive every event on your webhook. The API is compatible with WaSender API — most integrations only change the base URL and key.

Base URL   https://tekservix.com/wapenda/api
Format     JSON (UTF-8) in and out
Auth       Authorization: Bearer <key>

Authentication

There are two kinds of keys, both sent as a Bearer token:

KeyLooks likeUse it for
Device API keywpk_…Everything a single WhatsApp number does: sending, contacts, groups, status. Find it under Devices → API key.
Personal access tokenwpt_…Managing devices across your account (/whatsapp-sessions). Create it under API & tokens.
curl https://tekservix.com/wapenda/api/status -H "Authorization: Bearer wpk_your_device_key"

Responses & errors

Every response has success. Successful calls return data; failures return message (and errors for invalid fields).

{ "success": true,  "data": { "msgId": 58213, "jid": "250788123456@s.whatsapp.net", "status": "in_progress" } }
{ "success": false, "message": "\"to\" is required (phone number in international format, or a group/channel JID).", "errors": { "to": "required" } }
HTTPMeaning
400 / 422Invalid request — check message and errors.
401Missing or wrong key.
402Your plan is inactive or doesn’t cover this device.
403Wrong key type, or the device is suspended.
404Unknown endpoint or object.
409The device isn’t linked (scan the QR code first), or the action doesn’t fit the current state.
429Rate limit reached — see Retry-After.
503The device is reconnecting or the service is restarting. Retry shortly.

Limits & account protection

  • Requests: 300 per minute per key (X-RateLimit-Remaining header).
  • Messages: unlimited on every plan. Sends are queued and go out one at a time per device, so send-message answers immediately with "status": "in_progress"; track delivery with webhooks or message info.
  • Account protection (on by default, per device): random 5–12 s gaps between messages (1–3 s when replying inside a live conversation), a typing indicator, a warm-up for newly linked numbers (60 messages on day 1 rising to 1,500/day over 5 days), 250/hour, no identical message to the same person within 10 minutes, and a check that each new number is on WhatsApp. Turning it off allows ~1–2.5 s gaps and 10,000/day — only do that for opted-in traffic on an established number.
  • Media: up to 64 MB per file (WhatsApp itself limits images/video to ~16 MB).
WApenda links your number as a companion device (like WhatsApp Web), not through Meta’s official Business API. WhatsApp can ban numbers that send unwanted messages. Message people who expect to hear from you, and honour opt-outs — campaigns do this automatically.

Send a message

POST/send-message

One endpoint sends every message type. The aliases /send-image-message, /send-video-message, /send-document-message, /send-audio-message, /send-sticker-message, /send-contact-card, /send-location, /send-poll-message, /send-quoted-message, /send-message-with-mentions and /send-view-once-message accept the same body.

FieldTypeDescription
to *stringPhone number in international format (+250788123456 or 250788123456), a group JID (…@g.us) or a channel JID (…@newsletter).
textstringMessage text, or the caption for image/video/document.
imageUrl · videoUrl · audioUrl · documentUrl · stickerUrlURLPublic https link to the file (or a link from /upload). Stickers must be .webp.
fileNamestringName shown for documents.
mimeTypestringOptional override (detected automatically).
pttbooleanSend audio as a voice note.
contactobject{"name": "Aline", "phone": "+250788123456"}
locationobject{"latitude": -1.9441, "longitude": 30.0619, "name": "…", "address": "…"}
pollobject{"question": "…", "options": ["A","B"], "selectableCount": 1} (2–12 options)
replyTostring/intQuote an earlier message: its msgId or WhatsApp message id.
mentionsarrayPhone numbers to @mention (also write @250788123456 in the text).
viewOncebooleanImage/video/audio can be opened once.
curl -X POST https://tekservix.com/wapenda/api/send-message \
  -H "Authorization: Bearer $WAPENDA_KEY" -H "Content-Type: application/json" \
  -d '{"to":"+250788123456","text":"Hello Aline, your order is ready 🎉"}'

{ "success": true, "data": { "msgId": 58213, "jid": "250788123456@s.whatsapp.net", "status": "in_progress" } }

Examples by type

// Image with caption
{ "to": "+250788123456", "text": "New arrivals 👗", "imageUrl": "https://example.com/catalogue.jpg" }

// Document
{ "to": "+250788123456", "text": "Your invoice", "documentUrl": "https://example.com/inv-1042.pdf", "fileName": "Invoice-1042.pdf" }

// Voice note
{ "to": "+250788123456", "audioUrl": "https://example.com/hello.ogg", "ptt": true }

// Location
{ "to": "+250788123456", "location": { "latitude": -1.9441, "longitude": 30.0619, "name": "Our shop" } }

// Contact card
{ "to": "+250788123456", "contact": { "name": "Support", "phone": "+250788000000" } }

// Poll
{ "to": "120363000000000000@g.us", "poll": { "question": "Best delivery day?", "options": ["Monday", "Friday"] } }

// Reply to a message, with a mention
{ "to": "120363000000000000@g.us", "text": "Thanks @250788123456!", "replyTo": 58190, "mentions": ["250788123456"] }

// Older WaSender style (base64) is accepted too
{ "to": "+250788123456", "document": { "data": "JVBERi0xLjQK…", "filename": "report.pdf" }, "caption": "Report" }

Info, edit, delete, resend

{id} is the msgId returned when sending (or a WhatsApp message id).

GET/messages/{id}/info

Status (queued, sent, delivered, read, played, failed), error, WhatsApp key and timestamps.

PUT/messages/{id}

Edit your own text message: {"text": "corrected text"} (WhatsApp allows about 15 minutes).

DELETE/messages/{id}

Delete for everyone.

POST/messages/{id}/resend

Queue a failed message again.

Mark as read

POST/messages/read
{ "msgId": "3EB0X123456789", "jid": "250788123456@s.whatsapp.net" }
// or the key from a webhook
{ "key": { "id": "3EB0X123456789", "remoteJid": "250788123456@s.whatsapp.net", "fromMe": false } }

Typing indicator

POST/send-presence-update
{ "jid": "+250788123456", "type": "typing" }   // typing | recording | paused | available | unavailable

Upload & decrypt media

POST/upload

Multipart file, or JSON {"base64": "…", "fileName": "photo.jpg"}. Returns a publicUrl to use in imageUrl/documentUrl… Files are kept for 48 hours.

POST/decrypt-media

Send the webhook payload of a received image, video, audio or document ({"data": {"messages": {…}}}) and get a temporary publicUrl to the decrypted file.

Contacts

GET/on-whatsapp/{phone}

{"exists": true, "jid": "…@s.whatsapp.net"} — check before you message a new number.

GET/contacts

Contacts synced from the phone, plus people who messaged you.

GET/contacts/{phone}
PUT/contacts

{"phone": "+250788123456", "name": "Aline"} — save a name in WApenda.

GET/contacts/{phone}/picture
POST/contacts/{phone}/block · /unblock
GET/lid-from-pn/{phone} · /pn-from-lid/{lid}

WhatsApp sometimes identifies people by a private “LID” instead of a phone number. Webhooks always include cleanedSenderPn when the phone number is known, and you can reply to either form.

Groups

GET/groups
POST/groups

{"subject": "VIP customers", "participants": ["250788123456", "250722000000"]}

GET/groups/{groupJid}/metadata · /participants · /picture · /invite-link
POST/groups/{groupJid}/participants/add · /participants/remove

{"participants": ["250788123456"]} (you must be an admin)

PUT/groups/{groupJid}/participants/update

{"participants": ["250788123456"], "action": "promote"} — or demote.

PUT/groups/{groupJid}/settings

{"subject": "…", "description": "…", "announce": true, "restrict": false} — announce: only admins send; restrict: only admins edit info.

GET/groups/invite/{code}
POST/groups/invite/accept

{"inviteCode": "AbCdEf123"}

POST/groups/{groupJid}/leave

Status & user

GET/status

{"status": "connected"} — one of connected, connecting, need_scan, disconnected, logged_out, expired.

GET/user

The linked WhatsApp account: {"id", "number", "name"}.

Manage devices (personal access token)

GET/whatsapp-sessions

Your devices, including each api_key and webhook_secret.

POST/whatsapp-sessions
{ "name": "Sales line", "webhook_url": "https://your-app.com/wa", "webhook_events": ["messages.received","messages.update"],
  "account_protection": true, "log_messages": true, "read_incoming_messages": false, "auto_reject_calls": false }
GET/whatsapp-sessions/{id}
PUT/whatsapp-sessions/{id}
DELETE/whatsapp-sessions/{id}
POST/whatsapp-sessions/{id}/connect

{"method": "qr"}, or {"method": "code", "phone": "250788123456"} for an 8-character pairing code. Then poll:

GET/whatsapp-sessions/{id}/qrcode

{"qrCode": "2@…", "qrImage": "data:image/png;base64,…", "pairingCode": null, "status": "need_scan"} — the QR changes every ~20 s.

POST/whatsapp-sessions/{id}/disconnect · /restart · /regenerate-key
GET/whatsapp-sessions/{id}/message-logs?page=1&limit=20 · /session-logs

A device key may also call these for its own device id.

Webhooks

Set a webhook URL per device (dashboard or PUT /whatsapp-sessions/{id}). We send a POST with JSON for each event you selected, and retry 3 times (after ~4 s and 30 s) unless you answer 2xx. Answer quickly and process in the background.

POST https://your-app.com/wa
Content-Type: application/json
X-Webhook-Signature: <your device's webhook secret>
X-WApenda-Event: messages.received

{ "event": "messages.received", "sessionId": 12, "timestamp": 1758822000,
  "data": { "messages": {
    "key": { "id": "3EB0X123456789", "fromMe": false, "remoteJid": "250788123456@s.whatsapp.net",
             "addressingMode": "pn", "senderPn": "250788123456@s.whatsapp.net", "cleanedSenderPn": "250788123456" },
    "messageBody": "Hello, is this available?", "pushName": "Aline", "type": "text",
    "message": { "conversation": "Hello, is this available?" } } } }

Verify the request came from WApenda:

// Node.js (Express)
app.post('/wa', express.json(), (req, res) => {
  if (req.get('X-Webhook-Signature') !== process.env.WAPENDA_WEBHOOK_SECRET) return res.sendStatus(401);
  res.sendStatus(200);                       // acknowledge first
  handle(req.body).catch(console.error);     // then do the work
});

// PHP
if (!hash_equals(getenv('WAPENDA_WEBHOOK_SECRET'), $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '')) { http_response_code(401); exit; }
$event = json_decode(file_get_contents('php://input'), true);

Events

EventWhen
messages.receivedAny incoming message (chats, groups, channels).
messages-personal.received · messages-group.received · messages-newsletter.receivedIncoming, split by chat type.
messages.upsertAny new message, incoming or sent (including ones sent from the phone).
message.sentA message you queued was sent. Includes msgId and the WhatsApp key.
messages.updateStatus change: update.status 0 error, 2 sent, 3 delivered, 4 read, 5 played. Failed sends include update.error and your msgId.
message-receipt.updatePer-recipient receipts in groups.
messages.reaction · messages.deletedSomeone reacted to or deleted a message.
session.status{"status": "connected" | "connecting" | "need_scan" | "disconnected" | "logged_out" | "expired"}
qrcode.updatedA new QR (qr) or pairing code (pairingCode) is ready.
contacts.upsert · contacts.updateContacts synced or changed.
chats.upsert · chats.update · chats.deleteChat list changes.
groups.upsert · groups.update · group-participants.updateJoined a group, settings changed, members added/removed/promoted.
call.receivedIncoming voice/video call (from, isVideo). Turn on “Reject calls” to decline automatically.

WaSender compatibility

Coming from WaSender API? Change the base URL and use your WApenda key:

- https://www.wasenderapi.com/api
+ https://tekservix.com/wapenda/api
WaSenderWApenda
POST /send-message and the send-* aliases with to, text, imageUrl, videoUrl, documentUrl, audioUrl, stickerUrl, contact, location, poll, replyTo, mentions, viewOnceSame
Response {"msgId", "jid", "status": "in_progress"}Same
/messages/{id}/info, edit, delete, resend, /messages/read, /send-presence-update, /upload, /decrypt-mediaSame
/contacts…, /on-whatsapp, /lid-from-pn, /pn-from-lid, /groups…Same
/whatsapp-sessions… with a personal access tokenSame (connect supports qr and phone pairing codes)
Webhook events, X-Webhook-Signature, data.messages.key.cleanedSenderPnSame event names and fields; sessionId is your device id

Not available: Passkey (WebAuthn) linking — use the QR code or a phone pairing code.

Questions? Contact us — we usually answer within a business day.