OpenWA SDKs
Official client libraries for the OpenWA WhatsApp API Gateway.
All five SDKs are hand-written against the exact API surface (paths, DTOs,
response shapes) and unit-tested with mocked HTTP transports that assert on
the precise request URL, method, and body — so drift is caught at test time.
The wire types live in a dedicated module (types.ts / types.py / model/)
so they can later be regenerated by an OpenAPI codegen pass without touching the
hand-written resource methods.
| Language | Package | Notes |
|---|---|---|
| JavaScript / TypeScript | @rmyndharis/openwa |
dual ESM/CJS, bundled types |
| Python | rmyndharis-openwa |
sync (httpx), PEP 561 typed |
| PHP | rmyndharis/openwa |
sync (Guzzle, PHP 8.1+) |
| Java | com.rmyndharis:openwa |
sync (java.net.http + Gson, Java 17) |
| Go | github.com/rmyndharis/OpenWA/sdk/go |
stdlib-only, context-first, injectable transport (Go 1.22+) |
Coverage
All five SDKs expose the same fluent resource surface:
| Resource | Methods |
|---|---|
sessions |
list, get, getConfig, updateConfig, create, delete, start, stop, logout, forceKill, getQrCode, requestPairingCode, setOnlinePresence, stats |
messages |
list, sendText, sendImage/Video/Audio/Document/Sticker, sendLocation, sendContact, sendTemplate, sendPoll, reply, forward, react, delete, editMessage, history, reactions, media, pin, unpin, star, votePoll, sendBulk, batchStatus, cancelBatch |
contacts |
list, get, check, profilePicture, profilePictures, phone, upsert, delete, block, unblock, listBlocked |
groups |
list, get, create, joinGroup, joinInfo, add/remove/promote/demoteParticipants, setSubject, setDescription, get/updateGroupSettings, leave, getPicture, setPicture, deletePicture, inviteCode, revokeInviteCode, getMembershipRequests, approveMembershipRequests, rejectMembershipRequests |
webhooks |
list, listAll, deliveryFailures, get, create, update, delete, test |
chats |
list, markRead, markUnread, archive, pin, mute, clearMessages, delete, sendState, subscribePresence, getPresence |
labels |
list, get, chats, forChat, upsert, delete, addToChat, removeFromChat (WhatsApp Business) |
channels |
list, get, messages, create, delete, mute, subscribe, unsubscribe, demoteAdmin, transferOwnership (Newsletters) |
catalog |
info, products, product, sendProduct (WhatsApp Business) |
status |
list, fromContact, media, sendText, sendImage, sendVideo, sendVoice, delete (Stories) |
search |
search (Operator) |
templates |
list, get, create, update, delete |
profile |
setProfileName, setProfileStatus, setProfilePicture, deleteProfilePicture (OPERATOR) |
calls |
rejectCall, createLink (OPERATOR) |
media |
conversionStatus, convertVoice, convertVideo (OPERATOR) |
health |
check, live, ready |
⚠️ Endpoints requiring an
OPERATOR-level API key are noted in the inline docs. Deliberately not exposed, matchingdocs/18-sdk-design.mdexactly:auth/api-keys,audit,settings,stats,automation,infra,plugins, theintegrationmanagement routes,metrics,mcp,ingressanddocker. These two lists have to agree — they did not, in both directions, and a list that disagrees with its own design doc reads as an accidental omission rather than a decision.Everything else the gateway publishes is exposed. That sentence used to be an unqualified "all user-facing resources are", which was false for the session config routes and the two cross-session webhook reads until they were added.
One entry in the list above is special:
mcpis absent from the generatedopenapi.jsonitself, not just from the SDKs —POST /mcpis mounted directly on the Express adapter, outside the Nest/apirouting the Swagger scanner sees, so the exporter cannot emit it. Do not hand-add it toopenapi.json(npm run openapi:checkregenerates the file and the diff fails); the route is documented by hand indocs/06-api-specification.md, and the docs-contract gate namesPOST /mcpas the one heading allowed outside the contract.
JavaScript / TypeScript
npm install @rmyndharis/openwa
import { OpenWAClient } from '@rmyndharis/openwa';
const client = new OpenWAClient({
baseUrl: 'http://localhost:2785',
apiKey: 'owa_k1_…',
});
await client.sessions.start('my-session');
const result = await client.messages.sendText('my-session', {
chatId: '628123456789@c.us',
text: 'Hello from the OpenWA SDK!',
});
console.log(result.messageId);
Errors are typed — branch with instanceof:
import { OpenWANotFoundError, OpenWAConflictError } from '@rmyndharis/openwa';
try {
await client.messages.sendText(/* … */);
} catch (e) {
if (e instanceof OpenWAConflictError) {
/* engine not ready (409) */
}
}
Requires Node 18+ (uses the global
fetch). Pass a customfetchto the client constructor to intercept or observability-wrap requests.
Python
pip install rmyndharis-openwa
from openwa import OpenWAClient, OpenWANotFoundError
client = OpenWAClient(
base_url="http://localhost:2785",
api_key="owa_k1_…",
)
client.sessions.start("my-session")
result = client.messages.send_text("my-session", {
"chatId": "628123456789@c.us",
"text": "Hello from the OpenWA Python SDK!",
})
print(result["messageId"])
Pass transport=httpx.MockTransport(handler) for testing — no global
monkey-patching required.
PHP
composer require rmyndharis/openwa
<?php
use OpenWA\Client;
$client = new Client([
'baseUrl' => 'http://localhost:2785',
'apiKey' => 'owa_k1_…',
]);
$client->sessions->start('my-session');
$result = $client->messages->sendText('my-session', [
'chatId' => '628123456789@c.us',
'text' => 'Hello from the OpenWA PHP SDK!',
]);
echo $result['messageId'];
Requires PHP 8.1+ and Guzzle 7. For testing, inject a Guzzle client whose
handler is a MockHandler — no global state, no network.
Java
<dependency>
<groupId>com.rmyndharis</groupId>
<artifactId>openwa</artifactId>
<version>0.5.0</version>
</dependency>
import com.rmyndharis.openwa.OpenWAClient;
import com.rmyndharis.openwa.model.MessageResponse;
import com.rmyndharis.openwa.model.SendTextRequest;
OpenWAClient client = new OpenWAClient("http://localhost:2785", "owa_k1_…");
client.sessions.start("my-session");
MessageResponse result = client.messages.sendText("my-session",
SendTextRequest.builder()
.chatId("628123456789@c.us")
.text("Hello from the OpenWA Java SDK!")
.build());
System.out.println(result.messageId());
Requires Java 17+. Errors are a typed, unchecked hierarchy — branch with
instanceof OpenWANotFoundError / OpenWAConflictError. For testing, inject a
custom HttpTransport that records the request — no network. See
java/README.md for the full guide.
Go
go get github.com/rmyndharis/OpenWA/sdk/go
import (
"context"
"fmt"
"log"
openwa "github.com/rmyndharis/OpenWA/sdk/go"
)
client, err := openwa.New("http://localhost:2785", "owa_k1_…")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
client.Sessions.Start(ctx, "my-session")
res, err := client.Messages.SendText(ctx, "my-session", openwa.SendTextRequest{
ChatID: "628123456789@c.us",
Text: "Hello from the OpenWA Go SDK!",
})
fmt.Println(res.MessageID)
Requires Go 1.22+. Stdlib-only, context-first. Errors are typed — match with
errors.Is(err, openwa.ErrConflict) or unwrap *openwa.APIError with
errors.As. Inject an http.RoundTripper with openwa.WithTransport(...) for
testing, retry, tracing, or metrics. See go/README.md.
Reliability & security
- Use HTTPS in production. The API key is sent as
X-API-Keyon every request and is bearer-equivalent — never send it over plaintexthttp://outside local development. - No automatic retries by default. A failed request raises/throws
immediately; wrap calls in your own backoff if you need retries (especially
for
429). The injectable transport (fetch/transport/httpClient) is the extension point for retry or observability middleware. The Go client is the exception: it ships an opt-in policy (WithRetry(DefaultRetryPolicy())) that handles429/5xx, honorsRetry-After, and rewinds request bodies — still off unless you ask for it. - Redirects are never followed. A
3xxsurfaces to the caller rather than being followed, so the API key is never re-sent to a redirect target. - Default per-request timeout is 30s (configurable). Path segments (chat /
message ids) are percent-encoded; a base-URL path prefix (e.g. behind a proxy
at
/v1) is preserved.
Development
# JavaScript
cd sdk/javascript && npm test && npm run build && npm run smoke
# Python
cd sdk/python && python -m pytest -q
# PHP
cd sdk/php && composer install && ./vendor/bin/phpunit
# Java
cd sdk/java && mvn -B verify
# Go
cd sdk/go && go test -race ./... && go vet ./...
Each test suite mocks the HTTP layer and asserts on the exact path, so the
regression that originally shipped a broken messages/text path (the real path
is messages/send-text) can never recur silently.