Libraries
Five clients and three drop-in integrations, all maintained by us. Anything else generates cleanly from the OpenAPI document, which is published from the API itself.
What exists today
| Package | Status | |
|---|---|---|
| TypeScript | @kunshort/wavesenda | Official. Node 18+, ESM, zero runtime dependencies. |
| Python | wavesenda | Official. 3.9+, built on httpx. |
| PHP | wavesenda/wavesenda-php | Official. 8.1+, PSR-18 HTTP client. |
| Go | github.com/kunshort/wavesenda-go | Official. 1.21+, nothing outside the standard library. |
| MCP server | @kunshort/wavesenda-mcp | Official. Read-only by default. |
| OpenAPI | /api/schema/ | The generated document, for any other language. |
Integrations
For the cases where the point is not to write code at all. Each one sends through a number the merchant owns, so the messages arrive from the business rather than from a shortcode nobody recognises. Integrations covers installing and configuring each of them.
| Package | What it does | |
|---|---|---|
| n8n | @kunshort/n8n-nodes-wavesenda | A node plus a trigger, so an inbound message can start a workflow. |
| WordPress | wavesenda | Chat widget, form notifications and site alerts. |
| WooCommerce | wavesenda-for-woocommerce | Order updates and one-shot cart recovery. |
| Shopify | Connected app | The same for a Shopify store. Nothing to install. |
TypeScript
npm install @kunshort/wavesenda
import { WaveSenda } from '@kunshort/wavesenda'
const wa = new WaveSenda({
apiKey: process.env.WAVESENDA_API_KEY!,
// Retries 5xx, 429 and a disconnected session. Default 2.
maxRetries: 2,
timeoutMs: 30_000,
})
const sessions = await wa.sessions.list()
const health = await wa.sessions.health('support')
await wa.messages.send({
session: 'support',
to: '+237670000000',
text: 'Your order is ready for collection.',
idempotencyKey: 'order-1042-ready',
})
for (const conversation of (await wa.messages.list({ session: 'support' })).data) {
console.log(conversation.id)
}Failures throw a WaveSendaError carrying code, status and detail, so the branching you do in the SDK is the same branching you would do against the raw API.
import { WaveSendaError } from '@kunshort/wavesenda'
try {
await wa.messages.send({ session: 'support', to: number, text })
} catch (error) {
if (error instanceof WaveSendaError && error.code === 'recipient_not_on_whatsapp') {
await markNotReachable(number) // never retry this one
} else {
throw error
}
}Python
pip install wavesenda
import os
from wavesenda import WaveSenda, WaveSendaError
with WaveSenda(api_key=os.environ["WAVESENDA_API_KEY"]) as wa:
wa.messages.send(
session="support",
to="+237670000000",
text="Your order is ready for collection.",
idempotency_key="order-1042-ready",
)
# Pages for you — follow the cursor without writing the loop.
for message in wa.messages.iterate(session="support"):
process(message)WaveSendaError exposes .retryable, which is true for the transient cases — 5xx, 429 and a gateway that is momentarily unreachable — and false for the ones that will fail identically forever.
Webhook verification
Both SDKs ship the verifier, and both compare in constant time. Give them the raw request bytes: re-serialising the JSON changes the bytes, and the signature will never match.
import { verifyWebhook } from '@kunshort/wavesenda'
const ok = verifyWebhook(rawBody, req.header('X-WaveSenda-Signature'), secret)from wavesenda import verify_webhook
ok = verify_webhook(raw_body, request.headers.get("X-WaveSenda-Signature"), secret)MCP — WhatsApp for Claude and other agents
The MCP server exposes your inbox to any MCP client: Claude Desktop, Claude Code, or an agent you have written yourself. It is how you get “summarise what customers asked today” without building anything.
{
"mcpServers": {
"wavesenda": {
"command": "npx",
"args": ["-y", "@kunshort/wavesenda-mcp"],
"env": { "WAVESENDA_API_KEY": "ws_live_..." }
}
}
}| Tool | Does |
|---|---|
list_sessions | The connected numbers, with status and health. |
list_conversations | Recent conversations, to find who has been in touch. |
read_conversation | The messages in one thread, newest last. |
check_numbers | Which numbers have a WhatsApp account. |
session_health | The ban-risk report for one number. |
send_message | Only registered when WAVESENDA_ALLOW_SEND=true. |
WAVESENDA_ALLOW_SEND=true, and even then you can restrict it to particular numbers with WAVESENDA_ALLOWED_SESSIONS. An agent that can read your inbox is useful; one that can message your customers by accident is a liability.{
"mcpServers": {
"wavesenda": {
"command": "npx",
"args": ["-y", "@kunshort/wavesenda-mcp"],
"env": {
"WAVESENDA_API_KEY": "ws_live_...",
"WAVESENDA_ALLOW_SEND": "true",
"WAVESENDA_ALLOWED_SESSIONS": "support"
}
}
}
}Any other language
The OpenAPI 3.1 document is served from the API at /api/schema/, with a browsable reference at /api/docs/. It is generated from the code rather than maintained by hand, so it does not drift.
npx @openapitools/openapi-generator-cli generate \ -i https://api.wavesenda.com/api/schema/ \ -g go \ -o ./wavesenda-go