helliosolutions/helliosms

Official Laravel SDK for the Hellio Messaging API v1 (SMS, OTP, Voice, Number Lookup, Email Verification, USSD, Webhooks).

Maintainers

Package info

github.com/HellioSolutions/helliosms

pkg:composer/helliosolutions/helliosms

Transparency log

Statistics

Installs: 28

Dependents: 0

Suggesters: 0

Stars: 3

Open Issues: 0

v2.2.0 2026-07-07 12:10 UTC

This package is auto-updated.

Last update: 2026-07-07 19:44:58 UTC


README

tests Latest Version Total Downloads License

Laravel client for the Hellio Messaging API v1: SMS, OTP (SMS / voice / WhatsApp / email), Voice broadcasts, Number Lookup (HLR), Email Verification, USSD, Webhooks, plus a Laravel notification channel and a validation rule.

Install

composer require helliosolutions/helliosms

Publish the config (optional):

php artisan vendor:publish --tag=helliomessaging

Configure

Generate a token in your dashboard → Settings → API → Generate API token, then set:

HELLIO_BASE_URL=https://api.helliomessaging.com/v1
HELLIO_API_TOKEN=your-token-here
HELLIO_DEFAULT_SENDER=HellioSMS

Usage

Resolve the client from the container or use the HellioMessaging facade.

use Hellio\HellioMessaging\Facades\HellioMessaging;

// Account
HellioMessaging::balance();            // ['data' => ['balance' => '195.0000', 'available' => '194.65', ...]]
HellioMessaging::pricing('GH');        // optional ISO-2 country filter

// SMS (recipients: string, comma list, or array)
HellioMessaging::sms('233241234567', 'Hello!');
HellioMessaging::sms(['233241234567', '233201234567'], 'Hi all', 'HellioSMS');
HellioMessaging::message(1024);        // delivery status
HellioMessaging::campaign(1024);       // campaign summary

// OTP — sender (Sender ID) is REQUIRED for sms/voice and must be approved on your account.
// Optional length (4–10 digits) and expiry (minutes). Returns status "queued".
HellioMessaging::otp('233241234567', 'HellioSMS');                          // SMS
HellioMessaging::otp('233241234567', 'HellioSMS', 'voice');                 // Voice (TTS reads the code)
HellioMessaging::otp('233241234567', channel: 'whatsapp');                  // WhatsApp (no sender)
HellioMessaging::otp('233241234567', 'HellioSMS', length: 6, expiry: 10);   // custom length / expiry
HellioMessaging::otp('user@example.com', channel: 'email');                 // Email (no sender)
HellioMessaging::verify('233241234567', '123456');                          // bool
HellioMessaging::verifyOtp('user@example.com', '123456', 'email');          // full response

// Voice broadcast — text (we TTS it) or a hosted audio_url
HellioMessaging::voice('233241234567', 'HELLIO', text: 'Your code is 1 2 3 4');
HellioMessaging::voice(['233241234567'], 'HELLIO', audioUrl: 'https://cdn.example.com/promo.mp3');

// Number lookup (HLR) — async; poll results
HellioMessaging::lookup(['233241234567']);
HellioMessaging::lookups();
HellioMessaging::lookupResult(5);

// Email verification
HellioMessaging::verifyEmail(['user@gmail.com', 'bad@nodomain.invalid']);

// Webhooks (receive delivery reports)
HellioMessaging::createWebhook('https://your-app.com/hooks/hellio', ['message.delivered', 'message.failed']);
HellioMessaging::webhooks();
HellioMessaging::deleteWebhook(1);

USSD

Build interactive USSD services on your own dialer code. Reached through HellioMessaging::ussd() (needs an API token with the ussd ability). A USSD app holds your callback_url; an extension is the dialer code (e.g. *920*100#) you rent and point at the app. App and extension IDs are UUID strings. List endpoints are cursor-paginated (data + meta.next_cursor).

Apps have a test/live mode. A new app starts in test: you can simulate it in the sandbox by app_id straight away (no charge, no extension needed). To take it live, rent an extension for the app, then call setMode($id, 'live'). Each app carries a separate test_secret and live_secret (prefixed ussk_test_ / ussk_live_) for signing inbound callbacks. Extension rental draws from a dedicated USSD balance, separate from SMS credit and the main wallet.

// Pricing and availability
HellioMessaging::ussd()->pricing();               // short code + session/extension prices
HellioMessaging::ussd()->availability(100);       // ['data' => ['valid' => true, 'available' => true, ...]]

// Apps (your callback endpoints) - new apps start in "test" mode
$app = HellioMessaging::ussd()->createApp('Airtime top-up', 'https://your-app.com/ussd');
$id = $app['data']['id'];                          // UUID string
$testSecret = $app['data']['test_secret'];         // ussk_test_...  (verify sandbox callbacks)
$liveSecret = $app['data']['live_secret'];         // ussk_live_...  (verify live callbacks)
HellioMessaging::ussd()->apps();
HellioMessaging::ussd()->updateApp($id, 'Airtime top-up', 'https://your-app.com/ussd', true);
HellioMessaging::ussd()->deleteApp($id);

// Simulate a dial against your callback (always sandbox/test mode, by app_id).
// No real handset, no charge, no extension needed. serviceCode is optional and
// defaults to the shared short code.
$step = HellioMessaging::ussd()->simulate($id, 'sess-1', '233241234567', '', newSession: true);
// $step['data'] => ['message' => 'Welcome', 'action' => 'continue', 'continue' => true]

// Extensions (rent a dialer code from your USSD balance, optionally bound to an app)
$ext = HellioMessaging::ussd()->rentExtension(100, $id);
HellioMessaging::ussd()->extensions();
HellioMessaging::ussd()->releaseExtension($ext['data']['id']);

// Go live once the app has an extension, and rotate a secret when needed
HellioMessaging::ussd()->setMode($id, 'live');
HellioMessaging::ussd()->rotateSecret($id, 'live');   // "test" or "live"

// Sessions (audit trail)
HellioMessaging::ussd()->sessions('ended');
HellioMessaging::ussd()->session($id);

Things that can fail: renting a taken code throws ExtensionUnavailableException (409); a short USSD balance throws InsufficientBalanceException (402, insufficient_ussd_balance); switching an app to live before it has an extension throws ExtensionRequiredException (402, extension_required); simulating an app you don't own throws ValidationException (422, unknown_app).

Inbound callback

When a subscriber dials your extension, Hellio POSTs { sessionId, msisdn, serviceCode, input, sequence, mode } to the app's callback_url, signed with header X-Hellio-Signature = HMAC-SHA256(rawBody, secret). Use the secret that matches the request's mode: test_secret for sandbox/simulated traffic, live_secret once the app is live. Verify it and reply with { message, action } (action is continue or end):

public function handle(\Illuminate\Http\Request $request)
{
    $secret = $request->input('mode') === 'live'
        ? config('services.hellio.ussd_live_secret')
        : config('services.hellio.ussd_test_secret');
    $expected = hash_hmac('sha256', $request->getContent(), $secret);
    abort_unless(hash_equals($expected, $request->header('X-Hellio-Signature', '')), 403);

    return response()->json([
        'message' => 'Welcome to Airtime top-up',
        'action' => 'continue',
    ]);
}

Notification channel

use Hellio\HellioMessaging\Message\HellioMessagingSms;

class OrderShipped extends \Illuminate\Notifications\Notification
{
    public function via($notifiable): array { return ['helliomessaging']; }

    public function toHellioMessaging($notifiable): HellioMessagingSms
    {
        return (new HellioMessagingSms())
            ->message('Your order has shipped!')
            ->sender('HellioSMS');
    }
}

Route it on the notifiable:

public function routeNotificationForHelliomessaging($notification) { return $this->phone; }

Validation rule

Verify an OTP a user typed (defaults to the mobile_number field):

$request->validate([
    'mobile_number' => 'required',
    'otp' => 'required|hellio_otp:mobile_number',
]);

Error handling

Non-2xx responses throw typed exceptions (all extend HellioException):

Exception Status
InvalidApiTokenException 401
InsufficientBalanceException (incl. USSD insufficient_ussd_balance) 402
ExtensionRequiredException (USSD app needs an extension before going live) 402
ExtensionUnavailableException (USSD extension taken) 409
ValidationException (->response['errors']) 422
RateLimitException 429
ServiceUnavailableException 503
HellioException other
use Hellio\HellioMessaging\Exceptions\InsufficientBalanceException;

try {
    HellioMessaging::sms('233241234567', 'Hi');
} catch (InsufficientBalanceException $e) {
    // top up
}

Rate limit: 120 requests/minute per token.

License

MIT