Files

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, matching docs/18-sdk-design.md exactly: auth/api-keys, audit, settings, stats, automation, infra, plugins, the integration management routes, metrics, mcp, ingress and docker. 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: mcp is absent from the generated openapi.json itself, not just from the SDKs — POST /mcp is mounted directly on the Express adapter, outside the Nest /api routing the Swagger scanner sees, so the exporter cannot emit it. Do not hand-add it to openapi.json (npm run openapi:check regenerates the file and the diff fails); the route is documented by hand in docs/06-api-specification.md, and the docs-contract gate names POST /mcp as 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 custom fetch to 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-Key on every request and is bearer-equivalent — never send it over plaintext http:// 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 handles 429/5xx, honors Retry-After, and rewinds request bodies — still off unless you ask for it.
  • Redirects are never followed. A 3xx surfaces 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.