michotechnologies / michowapi-laravel
Laravel SDK for MichoWAPI - WhatsApp messaging gateway by Micho Technologies
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- illuminate/contracts: ^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
README
Official Laravel SDK for MichoWAPI — the WhatsApp messaging gateway by Micho Technologies.
Send text messages, upload media files, retrieve message history, and handle webhooks — all from your Laravel application using a clean, fluent API.
Requirements
- PHP 8.1+
- Laravel 10, 11, or 12
Installation
composer require michotechnologies/michowapi-laravel
Laravel auto-discovers the service provider and WApi facade automatically. No manual registration needed.
Publish the config file:
php artisan vendor:publish --tag=michowapi-config
Configuration
Add the following to your .env file:
MICHOWAPI_URL=https://wapi.michotech.me MICHOWAPI_KEY=mw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx MICHOWAPI_TIMEOUT=30 MICHOWAPI_MEDIA_TIMEOUT=120 MICHOWAPI_WEBHOOK_SECRET=your-webhook-secret
| Variable | Description | Default |
|---|---|---|
MICHOWAPI_URL |
Base URL of the MichoWAPI gateway | https://wapi.michotech.me |
MICHOWAPI_KEY |
Your API key (format: mw_ + 40 chars) |
— |
MICHOWAPI_TIMEOUT |
HTTP timeout in seconds for text requests | 30 |
MICHOWAPI_MEDIA_TIMEOUT |
Timeout for media file uploads | 120 |
MICHOWAPI_WEBHOOK_SECRET |
HMAC secret for verifying webhook payloads | — |
Note: Each API key is bound to one WhatsApp session automatically — you do not pass a session ID in requests. To use multiple WhatsApp numbers, generate a separate key per session and instantiate the client separately for each.
Getting Started
1. Create a Session
Log into the MichoWAPI dashboard, go to Sessions, click New Session, and scan the QR code with your phone via WhatsApp > Linked Devices.
2. Generate an API Key
Go to API Keys, click Generate API Key, select the session, and copy the key. It will not be shown again.
3. Send Your First Message
use MichoTech\WApi\Facades\WApi; WApi::messages()->sendText('260971000000', 'Hello from MichoWAPI!');
Usage
Via Facade
use MichoTech\WApi\Facades\WApi; WApi::messages()->sendText('260971000000', 'Hello!');
Via Dependency Injection
use MichoTech\WApi\MichoWapi; class NotificationController extends Controller { public function __construct(protected MichoWapi $wapi) {} public function send(): void { $this->wapi->messages()->sendText('260971000000', 'Hello!'); } }
Messages
Send Text
WApi::messages()->sendText('260971000000', 'Your exam results are ready.');
Phone numbers must include the country code and contain digits only (e.g. 260971000000, not +260 971 000 000).
Send Media
Send any file (image, video, audio, PDF, etc.) using sendMedia(). The file is uploaded directly — no public URL required.
// From a file path WApi::messages()->sendMedia('260971000000', '/path/to/photo.jpg', caption: 'School photo'); // From an SplFileInfo (e.g. from a Laravel uploaded file) $file = $request->file('attachment'); // UploadedFile extends SplFileInfo WApi::messages()->sendMedia('260971000000', $file, caption: 'Your invoice'); // From an open stream $stream = fopen('/path/to/audio.mp3', 'r'); WApi::messages()->sendMedia('260971000000', $stream, filename: 'voice-note.mp3');
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
$to |
string | Yes | Recipient phone number |
$file |
string / SplFileInfo / resource | Yes | File path, SplFileInfo, or stream |
$filename |
string|null | No | Override the filename shown to recipient |
$caption |
string|null | No | Caption shown below the media |
$sendAsDocument |
bool | No | Force the file to be sent as a document |
Send as Document
Use sendDocument() as a shortcut to always send as a document attachment:
WApi::messages()->sendDocument( '260971000000', '/path/to/report.pdf', filename: 'Term 2 Report Card.pdf', caption: 'Please find your results attached.', );
Send Bulk
Send the same text to multiple recipients. Returns results keyed by phone number.
$results = WApi::messages()->sendBulk( ['260971111111', '260972222222', '260973333333'], 'School fees for Term 3 are due. Log in to MichoSMS to view your balance.' ); foreach ($results as $phone => $result) { $messageId = $result['data']['message_id']; }
Message History
List Messages
// All messages (paginated) $messages = WApi::messages()->list(); // Filtered $messages = WApi::messages()->list([ 'from_me' => true, // outbound only 'type' => 'image', // chat | image | video | audio | document 'chat_id' => '260971000000@c.us', // specific contact 'page' => 2, ]);
Get a Single Message
$message = WApi::messages()->find('3EB0A0B4F3...'); // $message['data']['status'] can be: // queued | sent | delivered | read | failed
Received Messages
// Only inbound messages $received = WApi::messages()->received(page: 1);
Sync Messages
Pull in messages that arrived while the session was offline:
WApi::messages()->sync();
Messages in a Specific Chat
$chatMessages = WApi::messages()->forChat('260971000000@c.us', page: 1);
Webhooks
Configure
Set your webhook URL in the MichoWAPI dashboard under your session settings.
Register a route:
// routes/api.php Route::post('/webhooks/whatsapp', [WhatsAppWebhookController::class, 'handle']);
Exclude from CSRF in bootstrap/app.php (Laravel 11+):
->withMiddleware(function (Middleware $middleware) { $middleware->validateCsrfTokens(except: ['webhooks/*']); })
Handle Webhook Events
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use MichoTech\WApi\Facades\WApi; class WhatsAppWebhookController extends Controller { public function handle(Request $request) { // Verify the payload signature if (! WApi::webhooks()->verify($request)) { abort(403, 'Invalid webhook signature.'); } $event = WApi::webhooks()->parse($request); match ($event['event']) { 'message.received' => $this->onMessageReceived($event['data']), 'message.sent' => $this->onMessageSent($event['data']), 'session.connected' => $this->onConnected($event['session_id']), 'session.disconnected'=> $this->onDisconnected($event['session_id']), default => null, }; return response()->json(['status' => 'ok']); } protected function onMessageReceived(array $data): void { // $data['from'] — sender's number // $data['message'] — message text // $data['type'] — text, image, audio, document, etc. } }
Webhook Event Types
| Event | Description |
|---|---|
message.received |
Incoming message from a contact |
message.sent |
Outgoing message confirmed sent by WhatsApp |
session.connected |
Session successfully authenticated |
session.disconnected |
Session lost connection |
Webhook Payload Shape
{
"event": "message.received",
"session_id": 1,
"timestamp": "2026-03-08T10:30:00Z",
"data": {
"from": "260971000000",
"message": "Hi there!",
"type": "text"
}
}
Failed deliveries are retried up to 3 times with exponential backoff (1 min, 5 min, 15 min).
Error Handling
The SDK throws specific, typed exceptions for every error scenario:
use MichoTech\WApi\Facades\WApi; use MichoTech\WApi\Exceptions\AuthenticationException; use MichoTech\WApi\Exceptions\SessionNotConnectedException; use MichoTech\WApi\Exceptions\RateLimitException; use MichoTech\WApi\Exceptions\ValidationException; use MichoTech\WApi\Exceptions\MichoWapiException; try { WApi::messages()->sendText('260971000000', 'Hello!'); } catch (SessionNotConnectedException $e) { // WhatsApp session is disconnected — user needs to reconnect from dashboard Log::warning('WhatsApp session offline: ' . $e->getMessage()); } catch (RateLimitException $e) { // Too many requests — back off and retry Log::warning('MichoWAPI rate limit hit.'); } catch (ValidationException $e) { // Invalid request parameters Log::error('Validation failed: ' . $e->getMessage()); Log::error('Fields: ' . json_encode($e->errors())); // Get errors for a specific field: $toErrors = $e->errorsFor('to'); } catch (AuthenticationException $e) { // Bad API key, revoked key, or key not linked to a session Log::error('Auth error: ' . $e->getMessage()); } catch (MichoWapiException $e) { // Any other API or network error Log::error('WApi error: ' . $e->getMessage()); }
Exception Reference
| Exception | HTTP Status | Error Code(s) | Description |
|---|---|---|---|
SessionNotConnectedException |
422 | SESSION_NOT_CONNECTED |
Session not active |
RateLimitException |
429 | RATE_LIMITED |
Too many requests |
AuthenticationException |
401, 403 | UNAUTHORIZED, INVALID_API_KEY, NO_SESSION |
Auth failure |
ValidationException |
422 | VALIDATION_ERROR |
Invalid request params |
MichoWapiException |
any | any | Base class for all SDK errors |
Rate Limits
| Plan | Limit |
|---|---|
| Free | 60 requests/minute |
| Pro | 300 requests/minute |
| Enterprise | Custom |
Real-World Examples
MichoSMS: Notify Parents of Exam Results
use MichoTech\WApi\Facades\WApi; use MichoTech\WApi\Exceptions\SessionNotConnectedException; class SendResultsNotifications implements ShouldQueue { public function handle(): void { Student::with('guardian', 'results') ->whereHas('results') ->chunk(50, function ($students) { foreach ($students as $student) { try { WApi::messages()->sendText( $student->guardian->phone, "Dear {$student->guardian->name}, {$student->name}'s " . "Term 2 results are now available. Log in to MichoSMS to view them." ); } catch (SessionNotConnectedException) { // Stop immediately — no point continuing if session is down $this->fail('WhatsApp session is not connected.'); return false; } } }); } }
Send an Invoice PDF
use MichoTech\WApi\Facades\WApi; $pdfPath = storage_path("app/invoices/invoice-{$invoice->id}.pdf"); WApi::messages()->sendDocument( $client->phone, $pdfPath, filename: "Invoice-{$invoice->number}.pdf", caption: "Hi {$client->name}, please find your invoice attached.", );
Auto-Reply to Incoming Messages
// In your webhook controller protected function onMessageReceived(array $data): void { $from = $data['from']; $message = strtolower(trim($data['message'] ?? '')); $reply = match (true) { str_contains($message, 'results') => 'Log in to MichoSMS at michosms.com to view your results.', str_contains($message, 'fees') => 'Fees can be paid via mobile money. Reply FEES for details.', default => 'Thank you for your message. We will get back to you shortly.', }; WApi::messages()->sendText($from, $reply); }
License
MIT. See LICENSE for details.
About Micho Technologies
MichoWAPI is built and maintained by Micho Technologies, a software company based in Zambia building modern technology for the African market. Other products include MichoSMS (school management) and MyTutor ZM (peer-tutoring marketplace).