9.1 KiB
Ultra Message — Laravel WhatsApp Package Design
Date: 2026-05-19
Project: OperationModule (SteelERP) + reusable across future Laravel projects
Package name: promoseven/ultra-message
API provider: UltraMSG (https://docs.ultramsg.com/)
Overview
A reusable Laravel package that wraps the UltraMSG WhatsApp API. It ships as a standalone Composer package hosted on a private GitHub repo and is installed via VCS in any Laravel project. It integrates with Laravel's Notification system, supports queuing, exposes a Facade for one-liner sends, and handles incoming webhooks by firing a generic Laravel event. OperationModule adds a database-backed Settings UI so admins can configure credentials without touching .env.
1. Package Repository
- Repo name:
ultra-message(GitHub, under Promoseven org) - Composer name:
promoseven/ultra-message - Required in projects via
composer.jsonVCS entry pointing to the GitHub repo - Auto-discovered via Laravel's package discovery (
extra.laravel.providers)
2. Package File Structure
ultra-message/
├── src/
│ ├── UltraMessageServiceProvider.php
│ ├── UltraMessageClient.php
│ ├── UltraMessageChannel.php
│ ├── UltraMessageMessage.php
│ ├── UltraMessageException.php
│ ├── Facades/
│ │ └── UltraMessage.php
│ └── Events/
│ └── UltraMessageWebhookReceived.php
├── config/
│ └── ultra-message.php
├── routes/
│ └── webhook.php
├── tests/
│ ├── UltraMessageClientTest.php
│ └── UltraMessageChannelTest.php
├── composer.json
└── README.md
3. Configuration (config/ultra-message.php)
return [
'instance_id' => env('ULTRAMSG_INSTANCE_ID'),
'token' => env('ULTRAMSG_TOKEN'),
'webhook_secret' => env('ULTRAMSG_WEBHOOK_SECRET', null),
'webhook_path' => env('ULTRAMSG_WEBHOOK_PATH', 'ultra-message/webhook'),
'timeout' => env('ULTRAMSG_TIMEOUT', 30),
'enabled' => env('ULTRAMSG_ENABLED', true),
];
Dynamic config override: The service provider exposes UltraMessage::configUsing(callable $resolver). When set, the resolver is called at runtime to return an array of config values — used by OperationModule to read credentials from the database instead of .env.
// In OperationModule AppServiceProvider::boot()
UltraMessage::configUsing(fn() => [
'instance_id' => Setting::get('ultramsg_instance_id'),
'token' => Setting::get('ultramsg_token'),
'enabled' => Setting::get('ultramsg_enabled', true),
]);
4. The Client (UltraMessageClient)
Wraps all UltraMSG HTTP calls via Laravel's Http facade. Base URL: https://api.ultramsg.com/{instance_id}/messages/.
Outbound methods
sendText(string $to, string $message, ?string $replyId = null): array
sendImage(string $to, string $imageUrl, string $caption = ''): array
sendDocument(string $to, string $fileUrl, string $filename, string $caption = ''): array
sendAudio(string $to, string $audioUrl): array
sendVoice(string $to, string $audioUrl): array
sendVideo(string $to, string $videoUrl, string $caption = ''): array
sendSticker(string $to, string $stickerUrl): array
sendContact(string $to, string $contactId): array
sendLocation(string $to, float $lat, float $lng, string $address = ''): array
sendReaction(string $to, string $messageId, string $emoji): array
deleteMessage(string $messageId): array
Instance / account info
getInstanceStatus(): array
getChats(): array
getContacts(): array
getGroups(): array
Error handling
All methods throw UltraMessageException on HTTP failure (non-2xx) or when the UltraMSG response contains an error field. Callers catch one exception type.
If enabled is false in config, all send methods return early silently (no exception, no HTTP call).
5. Message DTO (UltraMessageMessage)
A fluent DTO used inside Laravel Notifications:
UltraMessageMessage::text('Order confirmed.')
UltraMessageMessage::image($url, 'Caption')
UltraMessageMessage::document($url, 'invoice.pdf', 'Your invoice')
UltraMessageMessage::audio($url)
UltraMessageMessage::video($url, 'Caption')
UltraMessageMessage::location($lat, $lng, 'Address')
UltraMessageMessage::contact($contactId)
Each static constructor sets the type and relevant properties. The ->to(string $number) method sets the recipient (overrides the notifiable's route).
6. Notification Channel (UltraMessageChannel)
Implements Illuminate\Notifications\Channels\Channel. When a Notification defines toUltraMessage(), this channel:
- Resolves the recipient from
$notifiable->routeNotificationFor('ultra_message')or from$message->to - Calls the correct
$client->send*()method based on$message->type - Supports
ShouldQueue— the notification queues normally via Laravel's queue system
Usage in OperationModule
class PurchaseOrderConfirmed extends Notification implements ShouldQueue
{
public function via($notifiable): array
{
return [UltraMessageChannel::class];
}
public function toUltraMessage($notifiable): UltraMessageMessage
{
return UltraMessageMessage::text("PO #{$this->order->number} confirmed.")
->to($notifiable->whatsapp_number);
}
}
7. Facade (UltraMessage)
Maps to UltraMessageClient for one-liner sends outside of the Notification system:
use PromoSeven\UltraMessage\Facades\UltraMessage;
UltraMessage::sendText('+971501234567', 'Your invoice is ready.');
UltraMessage::sendDocument('+971501234567', $pdfUrl, 'invoice.pdf', 'Invoice #123');
Also exposes:
UltraMessage::configUsing(callable $resolver); // dynamic config
UltraMessage::fake(); // test mode
UltraMessage::assertSent(callable $callback); // test assertion
8. Webhook Handling
The service provider registers POST /{webhook_path} (default: ultra-message/webhook) outside the auth middleware group, excluded from CSRF via VerifyCsrfToken.
On each request:
- If
webhook_secretis set — verify HMAC-SHA256 signature from theX-Hub-Signature-256header; return403on mismatch - Fire
UltraMessageWebhookReceivedevent with the full raw payload array - Return
200 OK
Consuming in OperationModule:
// In EventServiceProvider
protected $listen = [
UltraMessageWebhookReceived::class => [
HandleIncomingWhatsApp::class,
],
];
9. Testing Support
UltraMessage::fake();
// ... trigger code that sends ...
UltraMessage::assertSent(fn($msg) => $msg->to === '+971501234567' && $msg->type === 'text');
UltraMessage::assertNotSent();
UltraMessage::assertSentCount(3);
In fake mode, no HTTP calls are made. All sends are recorded in memory for assertion.
10. Settings UI in OperationModule
Database
A new settings table: id, key (unique), value, created_at, updated_at.
A Setting model with static helpers: Setting::get($key, $default) and Setting::set($key, $value).
Route & Controller
GET /settings/integrations → SettingsController@integrations
POST /settings/integrations/whatsapp → SettingsController@updateWhatsapp
Protected by auth, verified, and role:Admin.
View
A new Settings entry in the sidebar (Admin only) leading to an Integrations page with a WhatsApp section:
| Field | Input type |
|---|---|
| Enable WhatsApp | Toggle switch |
| Instance ID | Text input |
| API Token | Password input (masked, show/hide toggle) |
| Webhook Secret | Password input (masked, show/hide toggle) |
| Webhook URL | Read-only display (auto-generated from webhook_path) |
| Connection status | Badge — shows live status via getInstanceStatus() |
On save → toast success/error. "Test Connection" button calls getInstanceStatus() and shows result inline.
11. Integration Points in OperationModule
Once the package and settings UI are in place, notifications can be wired to these events:
| Trigger | Message |
|---|---|
| PO confirmed | Supplier: "Your PO #{number} has been confirmed." |
| GRN confirmed | Store: "GRN #{number} received and confirmed." |
| Sales order confirmed | Customer: "Your order #{number} is confirmed." |
| Invoice created | Customer: "Invoice #{number} is ready. Amount: {total}" |
| Delivery dispatched | Customer: "Your delivery is on the way." |
| Low stock alert | Store Manager: "Item {name} is below reorder level." |
| Production order completed | Production Manager: "PO #{number} completed." |
Each notification is a separate Notification class using UltraMessageChannel. This wiring is done in OperationModule, not in the package.
12. Implementation Phases
- Phase 1 — Package (
ultra-messagerepo): ServiceProvider, Client, Channel, Message DTO, Facade, Webhook handler, Fake/test support - Phase 2 — OperationModule integration:
settingstable +Settingmodel, Settings UI (sidebar + form), dynamic config boot, CSRF exclusion for webhook route - Phase 3 — Notifications: Wire individual notification classes for each ERP event listed above