Search by

Pure PHP SDK for the go-whatsapp-web-multidevice (GOWA) server

Package info

github.com/Gowa-PHP/sdk

pkg:composer/gowa-php/sdk

Statistics

Installs: 562

Dependents: 2

Suggesters: 0

Stars: 0

Open Issues: 0

v1.6.0 2026-09-14 09:54 UTC

README

gowa-php Banner

gowa-php

Pure PHP SDK for GOWA (go-whatsapp-web-multidevice) & WhatsApp Web, powered by whatsmeow

Latest Version Total Downloads Plumb score License PHP Version

🇧🇷 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:

Installation

composer require gowa-php/sdk

Requirements

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.
  • deviceId scoping: Every device-specific operation must be explicitly scoped by $deviceId. An invalid, empty, or missing deviceId would cause requests to execute on whichever device the server chooses.
  • Validation: All methods accepting $deviceId strictly reject empty or whitespace-only strings with InvalidArgumentException before any network request is issued.
  • Caller responsibility: The $deviceId must 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.