You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
OpenWA ships three official, hand-written client libraries for the REST API. They are not generated from an OpenAPI spec — each is written directly against the real API surface (paths, request DTOs, response shapes) and unit-tested with a mocked HTTP transport that asserts on the exact request path, method, and body, so contract drift is caught at test time rather than in production.
The import names differ from the dist names where the ecosystem requires it. Python installs rmyndharis-openwa but imports openwa; the client class is OpenWAClient in JS/Python and OpenWA\Client in PHP.
Design Principles
One client, fluent resources. A single client object (OpenWAClient / OpenWA\Client) exposes every resource as a property — client.messages.sendText(...), client.sessions.start(...). All three SDKs expose the same resource surface; only the language idioms differ (camelCase methods + objects in JS/PHP, snake_case methods + dicts in Python).
It is a request/response client, not an event SDK. There is no WebSocket, EventEmitter, or client.on(...). To receive inbound messages and acks, register a webhook (the webhooks resource) and host your own receiver, or connect to the real-time Socket.IO API directly (see API Specification §6.5).
Typed errors. Non-2xx responses raise/throw a typed error mapped from the HTTP status (401/403/404/409/429/501), plus a timeout error — all instanceof/catch-checkable. See each language's Error Handling subsection.
Injectable transport. The HTTP layer is replaceable (fetch in JS, an httpx transport in Python, a Guzzle client in PHP) — the extension point for retry/observability middleware and for testing without the network.
Safe by default. Redirects are never followed (so the API key is never re-sent to a redirect target), the auth/JSON headers always take precedence over caller-supplied defaults, path segments are percent-encoded, a base-URL path prefix (e.g. behind a reverse proxy at /v1) is preserved, and there is a default 30s per-request timeout. No automatic retries — wrap calls in your own backoff if you need them (especially for 429).
The operator/admin-only server modules — docker, metrics, infra, plugins, mcp — are intentionally not exposed in the SDKs; all user-facing resources above are. Methods that require an OPERATOR-level key are annotated OPERATOR in the per-language tables below.
18.2 TypeScript / JavaScript SDK
The official JavaScript/TypeScript SDK is published as @rmyndharis/openwa. It is a pure promise-based HTTP client: a single OpenWAClient exposes every API resource as a typed property. There is no event model — the SDK does not open WebSockets, emit events, or expose client.on(...). To receive inbound messages and acks, configure a webhook (see the webhooks resource) and host your own HTTP receiver.
The package ships dual CJS + ESM with bundled .d.ts types, so it is consumable from both require() and import.
Installation
npm install @rmyndharis/openwa
# or
yarn add @rmyndharis/openwa
# or
pnpm add @rmyndharis/openwa
Node 18+ required. The transport uses the global fetch (and AbortController), both built into Node 18 and later. To run on an older runtime, pass your own fetch implementation via the client constructor (see Client Configuration).
Quick Start
import{OpenWAClient}from'@rmyndharis/openwa';constclient=newOpenWAClient({baseUrl: 'http://localhost:2785',apiKey: 'owa_k1_…',});asyncfunctionmain(){// Start a session and bring the WhatsApp connection up.awaitclient.sessions.start('my-session');// Send a text message.constresult=awaitclient.messages.sendText('my-session',{chatId: '628123456789@c.us',text: 'Hello from the OpenWA SDK!',});console.log(result.messageId);// -> the WhatsApp message id}main();
sendText resolves to a MessageResponse ({ messageId: string; timestamp: number }). timestamp is the Unix epoch value returned by the API — seconds, passed through unchanged.
The constructor takes a single OpenWAClientOptions object. baseUrl and apiKey are required (the constructor throws synchronously if either is missing).
Option
Type
Required
Default
Description
baseUrl
string
yes
—
Base URL of the OpenWA API, e.g. http://localhost:2785. A trailing slash is trimmed; a path prefix (e.g. https://host/v1) is preserved.
apiKey
string
yes
—
API key sent as the X-API-Key header on every request.
timeoutMs
number
no
30000
Per-request timeout in milliseconds. Overridable per call via RequestOptions.timeoutMs on the raw request() method.
defaultHeaders
Record<string, string>
no
{}
Headers merged onto every request. The Content-Type: application/json and X-API-Key headers always take precedence.
fetch
FetchLike
no
globalThis.fetch
Injectable transport (the WHATWG fetch signature). Use this to wrap requests with retry/observability middleware, or to supply a fetch on runtimes that lack a global one.
Resources & Methods
All resources are accessed as properties on the client (client.<resource>.<method>). Every method returns a Promise. Methods marked OPERATOR require an API key with the OPERATOR role; a non-operator key receives a 403 (OpenWAForbiddenError).
The top-level client also exposes:
Method
Signature
Description
auth
client.auth()
Validate the configured API key and resolve its role ({ valid, role? }).
request
client.request<T>(options)
Raw escape hatch — issue an arbitrary request against the API.
sessions
Method
Signature
Description
list
list()
List all sessions (scoped to the key's allowedSessions).
get
get(id)
Get a single session by id.
create
create(body)
Create a new session (body.name required). OPERATOR
delete
delete(id)
Delete a session. OPERATOR
start
start(id)
Start a session and initialize the WhatsApp connection.
stop
stop(id)
Stop a session and disconnect gracefully.
forceKill
forceKill(id)
Force-kill a stuck session (SIGKILL + teardown).
getQrCode
getQrCode(id)
Get the current QR code for authentication (live from the engine).
requestPairingCode
requestPairingCode(id, body)
Request an 8-character pairing code for phone-based login.
stats
stats()
Aggregate statistics across the key's sessions.
messages
Method
Signature
Description
list
list(sessionId, query?)
List messages (filter by chat/sender); returns { messages, total }.
sendText
sendText(sessionId, { chatId, text })
Send a text message (text max 4096 chars).
sendImage
sendImage(sessionId, body)
Send an image (url or base64).
sendVideo
sendVideo(sessionId, body)
Send a video (url or base64).
sendAudio
sendAudio(sessionId, body)
Send an audio file (url or base64).
sendDocument
sendDocument(sessionId, body)
Send a document (url or base64; filename recommended).
sendSticker
sendSticker(sessionId, body)
Send a sticker (url or base64).
sendLocation
sendLocation(sessionId, body)
Send a location ({ chatId, latitude, longitude, description? }).
sendContact
sendContact(sessionId, body)
Send a contact card.
sendTemplate
sendTemplate(sessionId, body)
Render and send a stored message template.
reply
reply(sessionId, body)
Reply to a specific message.
forward
forward(sessionId, body)
Forward a message to another chat.
react
react(sessionId, body)
React to a message (empty reaction removes it).
delete
delete(sessionId, body)
Delete a message.
history
history(sessionId, chatId, query?)
Get message history for a chat (read live from WhatsApp).
reactions
reactions(sessionId, chatId, messageId)
Get reactions for a specific message.
sendBulk
sendBulk(sessionId, body)
Send a batch asynchronously (202 + batch id); poll via batchStatus.
batchStatus
batchStatus(sessionId, batchId)
Poll the status/progress of a bulk-send batch.
cancelBatch
cancelBatch(sessionId, batchId)
Cancel a running batch. OPERATOR
Media bodies share the SendMediaRequest shape: { chatId, url? | base64?, mimetype?, filename?, caption? } (url and base64 are mutually exclusive; base64 requires mimetype).
contacts
Method
Signature
Description
list
list(sessionId, query?)
List contacts known to the session.
get
get(sessionId, contactId)
Get details for a single contact by JID.
check
check(sessionId, number)
Check whether a phone number is registered on WhatsApp.
profilePicture
profilePicture(sessionId, contactId)
Get the contact's profile picture URL (or null).
phone
phone(sessionId, contactId)
Resolve a contact id (e.g. a @lid) to a phone number.
Update the group description (empty clears it). OPERATOR
leave
leave(sessionId, groupId)
Leave a group. OPERATOR
inviteCode
inviteCode(sessionId, groupId)
Get the group invite code and link.
revokeInviteCode
revokeInviteCode(sessionId, groupId)
Revoke the current invite code and generate a new one. OPERATOR
chats
Method
Signature
Description
list
list(sessionId, query?)
List active chats, most recent first.
markRead
markRead(sessionId, body)
Mark a chat as read/seen. OPERATOR
markUnread
markUnread(sessionId, body)
Mark a chat as unread. OPERATOR
delete
delete(sessionId, body)
Delete a chat from the chat list. OPERATOR
sendState
sendState(sessionId, body)
Send a chat presence state (typing/recording/paused). OPERATOR
webhooks
Method
Signature
Description
list
list(sessionId)
List all webhooks for a session.
get
get(sessionId, id)
Get a single webhook by id.
create
create(sessionId, body)
Create a new webhook. OPERATOR
update
update(sessionId, id, body)
Update a webhook. OPERATOR
delete
delete(sessionId, id)
Delete a webhook. OPERATOR
test
test(sessionId, id)
Trigger a test dispatch to the webhook URL and report the result. OPERATOR
labels(WhatsApp Business)
Method
Signature
Description
list
list(sessionId)
List all labels available in the business account.
get
get(sessionId, labelId)
Get a single label by id.
forChat
forChat(sessionId, chatId)
Get the labels currently applied to a chat.
addToChat
addToChat(sessionId, chatId, body)
Add a label to a chat. OPERATOR
removeFromChat
removeFromChat(sessionId, chatId, labelId)
Remove a label from a chat. OPERATOR
channels(Newsletters)
Method
Signature
Description
list
list(sessionId)
List all channels/newsletters the session is subscribed to.
get
get(sessionId, channelId)
Get a single channel by id.
messages
messages(sessionId, channelId, query?)
Get recent messages from a channel.
subscribe
subscribe(sessionId, body)
Subscribe to a channel using its invite code. OPERATOR
unsubscribe
unsubscribe(sessionId, channelId)
Unsubscribe from a channel. OPERATOR
catalog(WhatsApp Business)
Method
Signature
Description
info
info(sessionId)
Get the business catalog info.
products
products(sessionId, query?)
List catalog products; returns { products, pagination }.
product
product(sessionId, productId)
Get a single product by id.
sendProduct
sendProduct(sessionId, body)
Send a product message. OPERATOR
sendCatalog
sendCatalog(sessionId, body)
Send a catalog link message. OPERATOR
status(Stories)
Method
Signature
Description
list
list(sessionId)
Get all status updates ({ statuses }).
fromContact
fromContact(sessionId, contactId)
Get status updates from a specific contact.
sendText
sendText(sessionId, body)
Post a text status update. OPERATOR
sendImage
sendImage(sessionId, body)
Post an image status update. OPERATOR
sendVideo
sendVideo(sessionId, body)
Post a video status update. OPERATOR
delete
delete(sessionId, statusId)
Delete a status update by id. OPERATOR
This is WhatsApp "Status/Stories", distinct from session lifecycle status.
templates
Method
Signature
Description
list
list(sessionId)
List all templates for a session.
get
get(sessionId, id)
Get a single template by id.
create
create(sessionId, body)
Create a new template. OPERATOR
update
update(sessionId, id, body)
Update a template. OPERATOR
delete
delete(sessionId, id)
Delete a template. OPERATOR
health
Method
Signature
Description
check
check()
General health (also returns the running version).
live
live()
Kubernetes liveness probe ({ status }).
ready
ready()
Kubernetes readiness probe — checks both DB connections.
Error Handling
On a non-2xx response the SDK throws a typed OpenWAApiError subclass carrying .status (HTTP status), .body (parsed JSON error envelope, or raw text), and .errorKind (the NestJS error field). All error classes extend OpenWAError and are exported, so they are instanceof-checkable. A timeout throws OpenWATimeoutError, which extends OpenWAError directly (not OpenWAApiError).
Error class
HTTP status
Meaning
OpenWAAuthError
401
Missing or invalid API key.
OpenWAForbiddenError
403
The key's role is insufficient (e.g. an OPERATOR-only route).
OpenWANotFoundError
404
Resource not found.
OpenWAConflictError
409
Conflict — typically the engine is not ready.
OpenWARateLimitError
429
Rate limited.
OpenWANotImplementedError
501
The active engine does not support this operation.
OpenWAApiError
any other
Generic non-2xx (the base API error, e.g. 400; also surfaced for unfollowed 3xx redirects).
OpenWATimeoutError
—
The request exceeded the configured timeout.
import{OpenWAClient,OpenWAConflictError,OpenWANotFoundError,OpenWARateLimitError,OpenWATimeoutError,OpenWAApiError,}from'@rmyndharis/openwa';try{awaitclient.messages.sendText('my-session',{chatId: '628123456789@c.us',text: 'Hi!',});}catch(err){if(errinstanceofOpenWAConflictError){// 409 — engine not ready; start the session first.}elseif(errinstanceofOpenWANotFoundError){// 404 — session/chat does not exist.}elseif(errinstanceofOpenWARateLimitError){// 429 — back off and retry.}elseif(errinstanceofOpenWATimeoutError){// request timed out.}elseif(errinstanceofOpenWAApiError){console.error(`API error ${err.status}:`,err.body);}else{throwerr;// network/transport error}}
Notable Behaviors
Redirects are never followed. The transport uses redirect: 'manual', so a 3xx surfaces to the caller as an error (via OpenWAApiError) rather than being followed — this guarantees the X-API-Key header is never re-sent to a redirect target (potentially a different origin).
Auth and JSON headers take precedence. Request headers are merged in the order Content-Type: application/json → defaultHeaders → per-call headers → X-API-Key. The X-API-Key is applied last and cannot be overridden; Content-Type defaults to JSON.
Path segments are percent-encoded. Ids (session, chat, message, etc.) are encoded so a value cannot break out of its path position, while keeping the WhatsApp-safe characters @, :, and + readable (e.g. 628123456789@c.us).
Base-URL path prefix is preserved. A baseUrl such as https://gateway.example.com/v1 keeps its /v1 prefix on every request; only a trailing slash is trimmed.
No automatic retries. A failed request rejects immediately — the SDK does not retry, even on 429. Wrap calls in your own backoff if you need retries.
Injectable transport. Supply a custom fetch to wrap outbound calls with retry, logging, or observability middleware, or to run on a runtime without a global fetch. This is also the recommended way to unit-test without monkey-patching globals.
Dual CJS + ESM. The package exposes both an import (ESM) and require (CommonJS) entry point with bundled type declarations, so it works in either module system without configuration.
18.3 Python SDK
The Python SDK is a synchronous client built on httpx. It ships its own type hints (PEP 561) so editors and type-checkers see the full surface without stubs. There is no async client and no event/streaming model — every call is a blocking method that returns a plain dict/list (or None for a 204), and request payloads are plain dicts.
Installation
The PyPI distribution name and the import package differ:
pip install rmyndharis-openwa
fromopenwaimportOpenWAClient
Distribution (PyPI):rmyndharis-openwa
Import package:openwa
Client class:OpenWAClient
Python:>=3.9 (per pyproject.toml)
Runtime dependency:httpx>=0.25.0,<1.0
Typed: ships py.typed markers for openwa and openwa.resources (PEP 561)
Quick Start
fromopenwaimportOpenWAClientclient=OpenWAClient(
base_url="http://localhost:2785",
api_key="owa_k1_…",
)
# Create then start a sessionclient.sessions.create({"name": "my-session"})
client.sessions.start("my-session")
# Send a text messageresult=client.messages.send_text("my-session", {
"chatId": "628123456789@c.us",
"text": "Hello from the OpenWA Python SDK!",
})
print(result["messageId"])
client.close()
OpenWAClient is also a context manager, so the connection pool is closed for you:
Gateway base URL, e.g. http://localhost:2785. Raises ValueError if empty. A trailing / is stripped; any path prefix is preserved.
api_key
str
(required)
API key sent as the X-API-Key header. Raises ValueError if empty.
timeout
float
30.0
Per-request timeout in seconds. A breach raises OpenWATimeoutError.
default_headers
Mapping[str, str] | None
None
Extra headers applied to every request. Applied first, so the SDK's X-API-Key and Content-Type: application/json always win.
transport
httpx.BaseTransport | None
None
Optional httpx transport override (e.g. httpx.MockTransport) sharing the one connection pool. Useful for testing.
The client also exposes a few non-resource members:
Member
Signature
Description
auth
auth() -> AuthValidateResponse
POST /api/auth/validate — validate the configured API key.
request
request(method, path, *, query=None, body=None) -> Any
Raw escape hatch; path begins with /.
close
close() -> None
Close the underlying httpx.Client.
Resources & Methods
Resources are accessed as properties on the client (e.g. client.messages). All methods are snake_case; body/query arguments are plain dicts. Methods marked OPERATOR require an OPERATOR-level API key.
Every error inherits from OpenWAError. A non-2xx response raises an OpenWAApiError (or a more specific subclass picked by status); a timeout raises OpenWATimeoutError. The API-error classes carry .status (HTTP code), .body (parsed JSON or raw text), and .error_kind (the NestJS error field).
Exception
Trigger
OpenWAAuthError
HTTP 401 — missing or invalid API key
OpenWAForbiddenError
HTTP 403 — insufficient role
OpenWANotFoundError
HTTP 404 — resource not found
OpenWAConflictError
HTTP 409 — typically engine-not-ready
OpenWARateLimitError
HTTP 429 — too many requests
OpenWANotImplementedError
HTTP 501 — active engine doesn't support the operation
OpenWAApiError
any other non-2xx status (incl. unfollowed 3xx)
OpenWATimeoutError
request exceeded timeout (has a .timeout attribute)
fromopenwaimport (
OpenWAClient,
OpenWAConflictError,
OpenWANotFoundError,
OpenWARateLimitError,
OpenWATimeoutError,
OpenWAApiError,
)
client=OpenWAClient(base_url="http://localhost:2785", api_key="owa_k1_…")
try:
client.messages.send_text("my-session", {
"chatId": "628123456789@c.us",
"text": "Hi!",
})
exceptOpenWAConflictError:
print("Session engine not ready yet — start it first.")
exceptOpenWANotFoundError:
print("Session does not exist.")
exceptOpenWARateLimitError:
print("Rate limited — back off and retry.")
exceptOpenWATimeoutErrorase:
print(f"Timed out after {e.timeout}s")
exceptOpenWAApiErrorase:
print(f"API error {e.status} ({e.error_kind}): {e.body}")
Notable Behaviors
Redirects are never followed.follow_redirects is forced off so the X-API-Key header is never re-sent to a redirect target. An unfollowed 3xx therefore surfaces as an OpenWAApiError rather than a success.
Auth/JSON headers always win.default_headers are applied first; the SDK then sets Content-Type: application/json and X-API-Key, so caller headers can never clobber them.
Path segments are percent-encoded. Each path value (session/chat/message id, etc.) is encoded so a /, #, or ? can't break out of its position; already-safe id characters @, :, + are left readable. Boolean query params are serialized lowercase (true/false); None query values are dropped.
Base-URL path prefix is preserved. A trailing / is stripped from base_url, but any path prefix (e.g. when running behind a reverse proxy) is kept on every request.
No automatic retries. Each call issues exactly one request; retry/backoff is the caller's responsibility. Wrap or inject a custom transport for retry or observability middleware.
Testable transport injection. Pass transport=httpx.MockTransport(handler) to intercept requests in tests — no global monkey-patching.
204 / non-JSON handling. A 204 (or empty body) returns None; a 2xx body that isn't valid JSON is returned as raw text instead of raising.
PEP 561 typed. The package ships py.typed, so type-checkers consume the bundled hints directly.
18.4 PHP SDK
The PHP SDK is a hand-written, synchronous client built on Guzzle 7. It mirrors the full user-facing API surface: every resource method maps to one REST endpoint, request/response payloads are plain associative arrays, and non-2xx responses are translated into a typed OpenWA*Exception hierarchy.
The entry class is OpenWA\Client. It validates that baseUrl and apiKey are present (throwing OpenWAException otherwise), constructs the shared HTTP transport, and exposes each resource as a public property: $client->sessions, $client->messages, $client->contacts, $client->groups, $client->webhooks, $client->chats, $client->status, $client->health, $client->labels, $client->channels, $client->catalog, $client->templates.
Two escape hatches sit on the client itself:
Method
Signature
Description
auth
auth(): array
POST /api/auth/validate — validate the configured key and resolve its role.
Raw request against any path (advanced use); returns decoded JSON or null for empty/204.
Client Configuration
The constructor takes a single associative $config array:
Key
Type
Default
Description
baseUrl
string
— (required)
API base URL, e.g. http://localhost:2785. A trailing / is stripped; any path prefix (e.g. /v1 behind a proxy) is preserved.
apiKey
string
— (required)
Sent as the X-API-Key header on every request.
timeout
float
30.0
Per-request timeout in seconds.
httpClient
?\GuzzleHttp\ClientInterface
null
Inject a Guzzle client (e.g. one built on a MockHandler) for testing or middleware. When null, a default Guzzle client is created with the configured timeout.
defaultHeaders
array<string,string>
[]
Extra headers applied on every request, under the SDK's auth/JSON headers (which always win).
Missing baseUrl or apiKey throws OpenWA\Exceptions\OpenWAException from the constructor.
Resources & Methods
All payloads are associative arrays; all listed methods are synchronous and return the decoded JSON (an array), except the void deletes. Methods marked OPERATOR require an operator-level API key.
All exceptions live in OpenWA\Exceptions and descend from OpenWAException (which extends PHP's \Exception). Any non-2xx response is raised as an OpenWAApiException; the static classify() factory picks the most specific subclass by status code. An OpenWAApiException carries the HTTP status (getStatus(): int), the parsed error body (getBody(): mixed), and the NestJS error kind when present (getErrorKind(): ?string).
Exception
Extends
Trigger
OpenWAException
\Exception
Base for all SDK errors (also thrown for missing baseUrl/apiKey).
OpenWAApiException
OpenWAException
Any non-2xx (including unfollowed 3xx and other 4xx/5xx).
OpenWAAuthException
OpenWAApiException
401 — missing/invalid API key.
OpenWAForbiddenException
OpenWAApiException
403 — insufficient role (e.g. operator-only endpoint).
OpenWANotFoundException
OpenWAApiException
404 — resource not found.
OpenWAConflictException
OpenWAApiException
409 — conflict (e.g. engine not ready).
OpenWARateLimitException
OpenWAApiException
429 — rate limited.
OpenWANotImplementedException
OpenWAApiException
501 — active engine does not support the operation.
OpenWATimeoutException
OpenWAException
Request exceeded the timeout (getTimeout(): float). Not an API error — has no status/body.
<?phpuseOpenWA\Client;
useOpenWA\Exceptions\OpenWAConflictException;
useOpenWA\Exceptions\OpenWARateLimitException;
useOpenWA\Exceptions\OpenWATimeoutException;
useOpenWA\Exceptions\OpenWAApiException;
try {
$result = $client->messages->sendText('my-session', [
'chatId' => '628123456789@c.us',
'text' => 'Hello!',
]);
} catch (OpenWAConflictException$e) {
// 409 — engine not ready yet
} catch (OpenWARateLimitException$e) {
// 429 — back off and retry yourself (no auto-retry)
} catch (OpenWATimeoutException$e) {
fwrite(STDERR, "timed out after {$e->getTimeout()}s\n");
} catch (OpenWAApiException$e) {
// any other non-2xxfwrite(STDERR, "API {$e->getStatus()}: {$e->getMessage()}\n");
var_dump($e->getBody());
}
Notable Behaviors
Redirects are never followed. Guzzle is configured with allow_redirects => false, so a 3xx surfaces as an OpenWAApiException rather than being followed — the X-API-Key header is never re-sent to a redirect target.
Auth/JSON headers take precedence.defaultHeaders are merged in first, then X-API-Key, Content-Type: application/json, and Accept: application/json are applied on top, so they can't be clobbered.
Path segments are percent-encoded. Ids (chat/message/group ids, session names) pass through encodeSegment(), which rawurlencodes the value but keeps the WhatsApp-id-safe characters @, :, and + readable — so a value containing /, #, or ? cannot break out of its path position.
Base-URL path prefix is preserved. The base URL has its trailing / trimmed and requests are issued against an absolute baseUrl . $path; Guzzle's base_uri is intentionally unset, so a prefix like /v1 behind a reverse proxy is retained.
Null query values are dropped. Absent optional query parameters (null) are filtered out before the request, so they are never sent.
No automatic retries. A failed request throws immediately; wrap calls in your own backoff if you need retries (notably for 429). The injectable httpClient is the extension point for retry/observability middleware.
Empty/204 responses return null. A 204 or empty body decodes to null; resource methods that promise an array coalesce this to [] (or to the resource object for single-item gets).
Testing without the network. Inject a Guzzle client built on a GuzzleHttp\Handler\MockHandler via the httpClient config key — no global state, no live calls. The shipped test suite asserts on the exact path, method, and body.
PSR-4 autoloading. Everything lives under the OpenWA\ namespace mapped to src/; composer require rmyndharis/openwa wires up the autoloader.
18.5 n8n Community Node
OpenWA's n8n integration is not part of these SDK packages. It is a separate community node maintained in its own repository, which speaks the same REST + webhook contract documented in API Specification:
An action/HTTP path that calls the REST endpoints (e.g. POST /api/sessions/:id/messages/send-text) with the X-API-Key header.
A trigger path that registers a webhook (the webhooks resource) and receives inbound events, verifying the X-OpenWA-Signature HMAC.
For installation and node-by-node configuration, see the dedicated n8n Integration guide. Because the node consumes the public API contract, the verified route/event reference in docs 06 and §6.6 (Webhook Events) is the authority for what it can call and receive.
18.6 SDK Versioning & Releases
Versioning
The three SDKs are versioned independently of the gateway and of each other, each following SemVer. They are currently published at 0.1.0 while the gateway is at 0.7.3; an SDK version does not track the gateway version. Pin the SDK version your code is tested against and treat a major SDK bump as potentially breaking.
SDK
Registry
Package
JavaScript/TypeScript
npm
@rmyndharis/openwa
Python
PyPI
rmyndharis-openwa
PHP
Packagist
rmyndharis/openwa
Contract-drift protection
The wire types live in a dedicated module (types.ts / types.py) so they can later be regenerated by an OpenAPI codegen pass without touching the hand-written resource methods. Until then, each SDK's test suite mocks the HTTP transport and asserts on the exact request path, method, and body — so a drift between an SDK method and the real API (the class of bug that once shipped messages/text instead of the real messages/send-text) breaks a test rather than reaching users.
CI runs all three suites (path-filtered to sdk/**). The PHP package is mirrored to its own Packagist repository via a git subtree split, and that mirror publish is gated on the PHP tests passing, so a broken SDK cannot auto-publish. Bump the version, update the SDK's CHANGELOG, run the suite above, then tag/publish to the relevant registry.