rmyndharis / openwa
Official PHP SDK for OpenWA WhatsApp API Gateway
Requires
- php: >=8.1
- guzzlehttp/guzzle: ^7.9
Requires (Dev)
- phpunit/phpunit: ^10.0
README
Official PHP SDK for the OpenWA WhatsApp API Gateway.
A synchronous client built on Guzzle, PSR-4 autoloaded.
Install
composer require rmyndharis/openwa
Requires PHP 8.1+ and Guzzle 7. The namespace is OpenWA\.
Usage
<?php require 'vendor/autoload.php'; use OpenWA\Client; $client = new Client([ 'baseUrl' => 'https://your-gateway.example.com', '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'];
For tests, inject a Guzzle client whose handler is a MockHandler — no network, no global state:
$client = new Client([ 'baseUrl' => 'http://x', 'apiKey' => 'k', 'httpClient' => $mockGuzzleClient, ]);
Messaging
Voice notes: pass
'ptt' => truetosendAudioto send a real WhatsApp voice note (PTT). Supplyaudio/ogg; codecs=opusaudio for reliable playback; the server defaults the mimetype to that whenpttis set without one.
Errors
A non-2xx response throws a typed OpenWA\Exceptions\OpenWAApiException subclass —
OpenWAAuthException (401), OpenWAForbiddenException (403), OpenWANotFoundException (404),
OpenWAConflictException (409), OpenWARateLimitException (429),
OpenWANotImplementedException (501), OpenWAServiceUnavailableException (503 — the only
retryable one) — each exposing getStatus() and the parsed getBody().
A timeout throws OpenWATimeoutException.
use OpenWA\Exceptions\OpenWANotFoundException; try { $client->sessions->get('missing'); } catch (OpenWANotFoundException $e) { echo $e->getStatus(); // 404 }
Notes
- Use HTTPS in production — the API key is sent as
X-API-Keyand is bearer-equivalent. - The SDK does not retry, and never follows redirects (so the key is never re-sent to a redirect target). Path segments are percent-encoded; a base-URL path prefix (e.g. behind a reverse proxy) is preserved.
- Escape hatch for endpoints the SDK does not wrap:
$client->request($method, $path, $query, $body).
Releasing
Packagist installs this SDK from the mirror repository
rmyndharis/openwa-php, not from the
monorepo — Composer needs composer.json at a repository root and does not
support subdirectories. Two workflows keep that mirror correct:
split-php-sdk.ymlsyncs the mirror'smainon every push that touchessdk/php/**, which is what Packagist'sdev-mainfollows.php-sdk-release.ymlcuts a versioned release by pushing a tag to the mirror. Packagist derives versions from that repository's tags, so a tag in this monorepo alone publishes nothing.
There is no version field in composer.json, and there should not be —
Composer takes the version from the tag, and a hardcoded field drifts. The tag
is the version.
Cutting a release:
- If the minor line changes, update
extra.branch-alias.dev-mainincomposer.json(e.g.0.1.x-dev→0.2.x-dev) and land it onmain. The release workflow refuses to publish when the alias does not match the tag. - Tag that commit
php-sdk-v<version>(e.g.php-sdk-v0.5.0) and push the tag. The SDK has its own version line — the monorepo'sv*tags are the app version and never trigger an SDK release. - The workflow runs the test suite, then tags the mirror
<version>(novprefix, matching the existing tags there). Packagist picks it up from its GitHub hook within a minute or two.
A released version is effectively immutable. Packagist caches a tag's contents, so moving one does not reliably reach anyone who already resolved it. The workflow refuses to overwrite an existing mirror tag — bump the version instead of re-tagging.
Requires the PHP_SDK_SPLIT_TOKEN secret, the same one the mirror workflow
uses. Unlike the mirror workflow, which skips with a notice when the token is
absent, the release fails: a release that silently publishes nothing looks
exactly like one that worked.
License
MIT