gowa-php / sdk
Pure PHP SDK for the go-whatsapp-web-multidevice (GOWA) server
Requires
- php: ^8.2
- ext-hash: *
- ext-json: *
- guzzlehttp/guzzle: ^7.8 || ^8.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- pestphp/pest: ^3.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
gowa-php
Pure PHP SDK for GOWA (go-whatsapp-web-multidevice) & WhatsApp Web, powered by whatsmeow
🇧🇷 Para ler a documentação em Português, acesse README.pt.md.
⚡ Acknowledgments & Dependencies
This SDK interacts with the Go backend ecosystem created by the open-source community:
- whatsmeow — The underlying Go library created by Tulir Asokan that reverse-engineers the WhatsApp Web Multi-Device WebSocket protocol and Signal encryption.
- go-whatsapp-web-multidevice (GOWA) — The lightweight REST API wrapper created by Aldino Kemal exposing
whatsmeowover HTTP and Webhooks. - OpenAPI Specification — Official GOWA OpenAPI 3.0 specification (View in Swagger Editor).
Installation
composer require gowa-php/sdk
Requirements
- A running instance of GOWA (go-whatsapp-web-multidevice) REST server
- PHP >= 8.2
jsonandhashextensions- PSR-18 compliant HTTP Client or Guzzle Client
Usage Example
1. Initialize Configuration and Client
use Gowa\Sdk\Config; use Gowa\Sdk\GowaClient; $config = new Config( baseUrl: 'https://gowa.yourcompany.com', username: 'admin', password: 'secretpassword', timeout: 15 ); $client = new GowaClient($config); // Or inject a custom Guzzle handler (e.g. for testing or Laravel Http::fake() HandlerStack) without losing Config options: // $client = new GowaClient($config, handler: $customHandler);
2. Device Pairing (QR Code or 8-Digit Code)
// Register device and webhook $device = $client->createDevice( deviceId: 'my-instance-uuid', webhookUrl: 'https://myapi.com/webhooks/gowa/my-instance-uuid', webhookSecret: 'my_hmac_secret_48_chars', events: ['message', 'message.ack', 'message.reaction', 'message.edited', 'message.revoked'] ); // Start pairing via QR Code $pairing = $client->startQrPairing('my-instance-uuid'); echo $pairing->qrLink; // QR Code URL // Or request 8-digit code for manual typing on phone $codePairing = $client->startCodePairing('my-instance-uuid', '5511999998888'); echo $codePairing->pairCode; // e.g. ABCD-1234
3. Sending Messages and Media
Text, Links & Polls
// Send text $client->sendText('my-instance-uuid', '5511999998888', 'Hello! Message sent via gowa-php SDK.'); // Send URL link with preview $client->sendLink('my-instance-uuid', '5511999998888', 'https://fazz.ai', 'Check our website'); // Send interactive poll $client->sendPoll('my-instance-uuid', '5511999998888', 'What is your preferred time?', ['Morning', 'Afternoon', 'Evening']);
Media Uploads (Files, URLs, Streams & Voice Notes)
use Gowa\Sdk\Dto\MediaType; use Gowa\Sdk\Dto\MediaUpload; use Gowa\Sdk\Dto\MediaPayload; // Send voice note (PTT) from external URL $upload = MediaUpload::fromUrl('https://mycompany.com/storage/voicenote.m4a'); $media = new MediaPayload(type: MediaType::Audio, upload: $upload, voice: true); $client->sendMedia('my-instance-uuid', '5511999998888', $media); // Send local document $docUpload = MediaUpload::fromPath('/path/to/invoice.pdf'); $docMedia = new MediaPayload(type: MediaType::Document, upload: $docUpload); $client->sendMedia('my-instance-uuid', '5511999998888', $docMedia);
Message Actions (Forward, Edit, Revoke, Reactions, Star)
// Forward message $client->forwardMessage('my-instance-uuid', '5511999998888', 'WAMID_ORIGINAL_123'); // Edit sent text $client->editMessage('my-instance-uuid', '5511999998888', 'WAMID_ORIGINAL_123', 'Updated message text'); // Send emoji reaction $client->sendReaction('my-instance-uuid', '5511999998888', 'WAMID_ORIGINAL_123', '👍'); // Revoke (Delete for everyone) $client->revokeMessage('my-instance-uuid', '5511999998888', 'WAMID_ORIGINAL_123'); // Star or unstar message $client->starMessage('my-instance-uuid', '5511999998888', 'WAMID_ORIGINAL_123', true);
Media Download (Echo Candidate Phones, Timeout, and Cleanup)
use Gowa\Sdk\Exceptions\MediaUnavailableException; // 1. Query media with candidate phones (e.g. echo outbound vs inbound recipient) try { $remoteMedia = $client->describeMedia( deviceId: 'my-instance-uuid', phones: ['5511888888888', '5511999999999'], // tries candidate phones in order providerMessageId: 'WAMID_ORIGINAL_123' ); } catch (MediaUnavailableException $e) { // Media was permanently refused (e.g. text-only message or expired) $remoteMedia = null; } // 2. Download decrypted media with custom timeout (default is Config timeout) if ($remoteMedia !== null) { $client->downloadMedia( mediaUrl: $remoteMedia->url, destinationPath: '/path/to/downloaded.mp4', timeout: 120 // dedicated timeout for large media downloads ); }
4. Webhook Verification & Event Parsing
use Gowa\Sdk\Dto\EventPayload; use Gowa\Sdk\Dto\LiveLocationPayload; use Gowa\Sdk\Dto\PollPayload; use Gowa\Sdk\Security\WebhookSignature; use Gowa\Sdk\Webhook\Dto\IncomingAck; use Gowa\Sdk\Webhook\Dto\IncomingMessage; use Gowa\Sdk\Webhook\Event; use Gowa\Sdk\Webhook\WebhookParser; $payload = file_get_contents('php://input'); // GOWA sends the signature header formatted as "sha256=<hex>" $signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? ''; $secret = 'my_hmac_secret_48_chars'; // 1. Verify HMAC SHA-256 signature (requires the "sha256=" prefix) if (!WebhookSignature::verify($payload, $signature, $secret)) { http_response_code(401); exit('Invalid signature'); } // 2. Parse incoming webhook payload with fluent routing WebhookParser::parse($payload) ->onMessage(function (IncomingMessage $msg) { $msg ->whenLiveLocation(function (LiveLocationPayload $loc) { echo "Coordinates: {$loc->latitude}, {$loc->longitude}\n"; }) ->whenPoll(function (PollPayload $poll) { echo "Poll question: {$poll->question}\n"; }) ->whenEvent(function (EventPayload $event) { echo "Event title: {$event->name}\n"; }) ->whenText(function (string $text) { echo "Text: {$text}\n"; }) ->otherwise(function (IncomingMessage $msg) { echo "Other message type: {$msg->type}\n"; }); }) ->onAck(function (IncomingAck $ack) { echo "Receipt: {$ack->receiptType}\n"; }) ->otherwise(function (mixed $data, Event $event) { echo "Unhandled or unknown event: {$event->value}\n"; }); // Traditional array access and helper methods remain fully supported: // $event = WebhookParser::parse($payload); // if ($event->isMessage()) { $msg = $event->message(); ... } // or $event['event'] === Event::Message
Available Features Summary
Device Management
| Feature | Method | Endpoint |
|---|---|---|
| Register Device & Webhook | createDevice() |
POST /devices |
| Update Webhook Config | updateWebhook() |
PATCH /devices/:id/webhook |
| Start QR Pairing | startQrPairing() |
GET /devices/:id/login |
| Start 8-Digit Code Pairing | startCodePairing() |
POST /devices/:id/login/code |
| Query Device Info & Status | device() |
GET /devices/:id |
| Logout Device | logout() |
POST /devices/:id/logout |
Messages & Interactions
| Feature | Method | Endpoint |
|---|---|---|
| Text Message | sendText() |
POST /send/message |
| Image | sendMedia() |
POST /send/image |
| Video | sendMedia() |
POST /send/video |
| Audio / PTT Voice Note | sendMedia() (voice: true) |
POST /send/audio |
| Document / File | sendMedia() |
POST /send/file |
| WebP Sticker | sendSticker() |
POST /send/sticker |
| Location | sendLocation() |
POST /send/location |
| Contact Card | sendContacts() |
POST /send/contact |
| URL Link Preview | sendLink() |
POST /send/link |
| Interactive Poll | sendPoll() |
POST /send/poll |
| Emoji Reaction | sendReaction() |
POST /message/:id/reaction |
| Forward Message | forwardMessage() |
POST /message/:id/forward |
| Edit Message | editMessage() |
POST /message/:id/update |
| Revoke Message (Delete for All) | revokeMessage() |
POST /message/:id/revoke |
| Delete Message (Local) | deleteMessage() |
POST /message/:id/delete |
| Star / Unstar Message | starMessage() |
POST /message/:id/star, POST /message/:id/unstar |
| Mark Audio Played | markPlayed() |
POST /message/:id/played |
| Mark Read / Typing | markRead() |
POST /message/:id/read |
Contacts & Media Download
| Feature | Method | Endpoint |
|---|---|---|
| Contact Profile Picture | avatar() |
GET /user/avatar |
| Prepare Media Download | describeMedia() |
GET /message/:id/download |
| Download Decrypted Media | downloadMedia() |
GET media URL |
Error Handling
The SDK provides structured exceptions to clearly differentiate between network/transport unreachability, permanent media refusals, and server validation/refusal responses:
use Gowa\Sdk\Exceptions\GowaRequestException; use Gowa\Sdk\Exceptions\GowaUnreachableException; use Gowa\Sdk\Exceptions\MediaUnavailableException; try { $client->sendText('my-device-id', '5511999998888', 'Hello'); } catch (GowaUnreachableException $e) { // Network failure, connection timeout, DNS failure, or server unreachable // Reconcile delivery status before retrying non-idempotent operations (such as sending messages) } catch (MediaUnavailableException $e) { // Permanent media download refusal (e.g. message does not contain media or unsupported format) } catch (GowaRequestException $e) { // Server responded with an error (4xx/5xx or code != SUCCESS) $status = $e->statusCode; // e.g. 400 $code = $e->gowaCode; // e.g. "VALIDATION_ERROR" $message = $e->gowaMessage; // e.g. "your audio type is not allowed..." }
🔒 Security & Multi-Tenancy Considerations
Tenant Isolation & Device Scoping
- GOWA does not isolate tenants at the API level: A single Basic Auth credential provides access to all paired devices on the server.
deviceIdscoping: Every device-specific operation must be explicitly scoped by$deviceId. An invalid, empty, or missingdeviceIdwould cause requests to execute on whichever device the server chooses.- Validation: All methods accepting
$deviceIdstrictly reject empty or whitespace-only strings withInvalidArgumentExceptionbefore any network request is issued. - Caller responsibility: The
$deviceIdmust always originate from trusted server storage (e.g. your database model) and never directly from unvalidated user input or request parameters. - Un-scoped endpoints: Broad reading endpoints (
/chats,/user/my/contacts,/user/my/groups) are not scoped per device on the GOWA server and return mixed data across all paired numbers. Avoid relying on them for tenant-isolated data.
Anti-SSRF Validation & Redirect Protection
Any media or QR image URL fetched through downloadMedia() or fetchQrImage() is strictly validated via GowaHost::assertBelongsToServer() to ensure requests only target the configured GOWA server. Furthermore, automatic HTTP redirects are explicitly disabled (allow_redirects: false) to prevent unvalidated redirects from escaping the configured host, protecting against SSRF attacks and credential leaks.
Running Tests (Pest PHP)
vendor/bin/pest
⚠️ Disclaimer & Terms of Use
This software is an open-source library created for educational, research, and testing laboratory purposes.
- Third-Party Terms of Service: Users of this library are solely responsible for complying with WhatsApp's Terms of Service, Meta's Platform Policies, and the terms of any third-party services utilized.
- Automated Messaging & Policy Compliance: Automated or unauthorized messaging may violate platform terms. Users must ensure strict compliance with applicable privacy laws (e.g., GDPR, LGPD), user consent requirements, and platform guidelines.
- No Warranty & Liability: This software is provided "as is", without warranty of any kind, express or implied. The authors and contributors assume no liability for any account bans, data loss, service interruptions, or misuse of this library.
Contributing
Please see CONTRIBUTING.md and CODE_OF_CONDUCT.md for details.
License
This package is open-source software licensed under the MIT License.