Hellio.Messaging 1.2.0

dotnet add package Hellio.Messaging --version 1.2.0
                    
NuGet\Install-Package Hellio.Messaging -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Hellio.Messaging" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Hellio.Messaging" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Hellio.Messaging" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Hellio.Messaging --version 1.2.0
                    
#r "nuget: Hellio.Messaging, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Hellio.Messaging@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Hellio.Messaging&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Hellio.Messaging&version=1.2.0
                    
Install as a Cake Tool

Hellio Messaging - Official .NET SDK

tests NuGet Downloads License

.NET client for the Hellio Messaging API v1: SMS, OTP (SMS / email / voice), Voice broadcasts, Number Lookup (HLR), Email Verification, USSD, and Webhooks.

Targets netstandard2.0 and net8.0, so it runs on .NET Framework 4.6.1+, .NET Core, Xamarin, and modern .NET.

Install

dotnet add package Hellio.Messaging

Or with the Package Manager Console:

Install-Package Hellio.Messaging

Configure

Generate a token in your dashboard (Settings, then API, then Generate API token), then construct the client. You can pass values directly or set the environment variables HELLIO_API_TOKEN, HELLIO_BASE_URL, and HELLIO_DEFAULT_SENDER.

using Hellio.Messaging;

var hellio = new HellioClient(
    token: "your-token-here",
    baseUrl: "https://api.helliomessaging.com/v1", // optional, this is the default
    defaultSender: "HellioSMS");                   // optional Sender ID for SMS

Reading from the environment instead:

// Uses HELLIO_API_TOKEN, HELLIO_BASE_URL, HELLIO_DEFAULT_SENDER when arguments are omitted.
var hellio = new HellioClient();

Every call returns a System.Text.Json.JsonElement (payloads live under a data key), except VerifyAsync, which returns a bool, and the Ussd methods, which return typed models (see USSD). All methods are async and accept an optional CancellationToken.

Usage

using Hellio.Messaging;
using System.Text.Json;

var hellio = new HellioClient(token: "your-token-here", defaultSender: "HellioSMS");

// Account
JsonElement balance = await hellio.BalanceAsync();   // data.balance, data.available, ...
JsonElement pricing = await hellio.PricingAsync("GH"); // optional ISO-2 country filter

// SMS (recipients: single string, comma list, or IEnumerable<string>)
await hellio.SendSmsAsync("233241234567", "Hello!");
await hellio.SendSmsAsync(new[] { "233241234567", "233201234567" }, "Hi all", "HellioSMS");
await hellio.MessageAsync(1024);   // delivery status
await hellio.CampaignAsync(1024);  // campaign summary

// OTP - sender (Sender ID) is REQUIRED for sms/voice and must be approved on your account.
// Optional length (4 to 10 digits) and expiry (minutes). Returns status "queued".
await hellio.SendOtpAsync("233241234567", "HellioSMS");                       // SMS
await hellio.SendOtpAsync("233241234567", "HellioSMS", channel: "voice");     // Voice (TTS reads the code)
await hellio.SendOtpAsync("233241234567", "HellioSMS", length: 6, expiry: 10); // custom length / expiry
await hellio.SendOtpAsync("user@example.com", channel: "email");              // Email (no sender)

bool ok = await hellio.VerifyAsync("233241234567", "123456");                 // bool convenience
JsonElement res = await hellio.VerifyOtpAsync("user@example.com", "123456", "email"); // full response

// Voice broadcast - text (we TTS it) or a hosted audioUrl
await hellio.SendVoiceAsync("233241234567", "HELLIO", text: "Your code is 1 2 3 4");
await hellio.SendVoiceAsync(new[] { "233241234567" }, "HELLIO", audioUrl: "https://cdn.example.com/promo.mp3");
await hellio.VoiceStatusAsync(2048);

// Number lookup (HLR) - async; poll results
await hellio.LookupAsync(new[] { "233241234567" });
await hellio.LookupsAsync();
await hellio.LookupResultAsync(5);

// Email verification
await hellio.VerifyEmailAsync(new[] { "user@gmail.com", "bad@nodomain.invalid" });

// Webhooks (receive delivery reports)
await hellio.CreateWebhookAsync("https://your-app.com/hooks/hellio",
    new[] { "message.delivered", "message.failed" });
await hellio.WebhooksAsync();
await hellio.DeleteWebhookAsync(1);

Reading responses

Responses are JsonElement, so you can navigate them directly:

JsonElement balance = await hellio.BalanceAsync();
string available = balance.GetProperty("data").GetProperty("available").GetString();

USSD

USSD lives under hellio.Ussd and needs a token with the ussd ability. Unlike the rest of the SDK, these methods return typed models (for example UssdApp, UssdExtension, UssdSession) rather than a raw JsonElement. List methods accept an optional cursor for pagination.

Apps have two modes, test and live, each with its own signing secret (test_secret, prefix ussk_test_; live_secret, prefix ussk_live_). New apps start in test mode. The typical lifecycle is: create the app, simulate the flow against your callback URL (sandbox, addressed by appId), rent an extension from your USSD balance, then switch the app to live. USSD money is a dedicated balance, separate from SMS credit and the main wallet.

using Hellio.Messaging;

var hellio = new HellioClient(token: "your-token-here");

// Pricing and availability
UssdPricing pricing = await hellio.Ussd.PricingAsync();
UssdAvailability check = await hellio.Ussd.AvailabilityAsync("100");
if (check.Valid && check.Available)
{
    // check.MonthlyPrice holds the rental cost
}

// 1. Create an application. The response carries both signing secrets and starts in test mode.
UssdApp app = await hellio.Ussd.Apps.CreateAsync("Airtime top-up", "https://your-app.com/ussd");
string appId = app.Id!;                 // a UUID string
string testSecret = app.TestSecret!;    // ussk_test_...: verify sandbox callback signatures
string liveSecret = app.LiveSecret!;    // ussk_live_...: verify live callback signatures
// app.Mode == "test", app.IsLive == false

await hellio.Ussd.Apps.UpdateAsync(appId, "Airtime top-up", "https://your-app.com/ussd", active: true);
IReadOnlyList<UssdApp> apps = await hellio.Ussd.Apps.ListAsync();

// 2. Simulate the flow. Always runs in the sandbox (test mode) and is addressed by appId.
//    Start with newSession: true, then pass the reference back on later steps.
//    serviceCode is optional; omit it to use the shared short code.
UssdSimulateResult step1 = await hellio.Ussd.SimulateAsync(
    appId: appId, msisdn: "233241234567", newSession: true);
UssdSimulateResult step2 = await hellio.Ussd.SimulateAsync(
    appId: appId, msisdn: "233241234567", input: "1", sessionId: "sess-1");
// step.Message is shown to the subscriber; step.Continue is false when the session ends.
// An app you do not own returns ValidationException (422, error "unknown_app").

// 3. Rent an extension (a dialable suffix under the shared short code). Drawn from your USSD balance.
try
{
    UssdExtension ext = await hellio.Ussd.Extensions.RentAsync("100", appId: appId);
    // ext.DialString is what subscribers dial, e.g. *920*100#
}
catch (ConflictException)            // 409: the code is already taken
{
}
catch (InsufficientBalanceException) // 402 "insufficient_ussd_balance": top up your USSD balance
{
}
IReadOnlyList<UssdExtension> extensions = await hellio.Ussd.Extensions.ListAsync();

// 4. Go live once an extension is in place.
try
{
    UssdApp live = await hellio.Ussd.Apps.SetModeAsync(appId, "live");   // live.IsLive == true
}
catch (ExtensionRequiredException)   // 402 "extension_required": rent an extension first
{
}

// Rotate a signing secret when needed ("test" or "live").
UssdApp rotated = await hellio.Ussd.Apps.RotateSecretAsync(appId, "live");
string newLiveSecret = rotated.LiveSecret!;

// Sessions
IReadOnlyList<UssdSession> ended = await hellio.Ussd.Sessions.ListAsync(status: "ended");
UssdSession session = await hellio.Ussd.Sessions.GetAsync("99999999-0000-0000-0000-000000000009");

// Clean up
await hellio.Ussd.Extensions.ReleaseAsync("33333333-0000-0000-0000-000000000003");
await hellio.Ussd.Apps.DeleteAsync(appId);

Handling USSD callbacks

When a subscriber dials your extension, Hellio POSTs a JSON body to your app's callback_url:

{ "sessionId": "...", "msisdn": "...", "serviceCode": "...", "input": "...", "sequence": 1, "mode": "..." }

The request is signed with an X-Hellio-Signature header holding HMAC-SHA256(rawBody, appSecret), where appSecret is the secret for the mode the request came in on: app.TestSecret for sandbox/simulator traffic and app.LiveSecret for live dials (the mode field in the body tells you which). Verify it, then reply with { "message": ..., "action": ... } where action is continue or end.

using System.Security.Cryptography;
using System.Text;

static bool SignatureIsValid(string rawBody, string signatureHeader, string appSecret)
{
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(appSecret));
    var computed = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
    var expected = Convert.ToHexString(computed).ToLowerInvariant();
    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(expected),
        Encoding.UTF8.GetBytes(signatureHeader));
}

// Then return, for example:
// { "message": "Welcome to Airtime top-up\n1. Buy\n2. Balance", "action": "continue" }

Error handling

Non-2xx responses throw typed exceptions (all extend HellioException). Each carries the HTTP StatusCode and the parsed Response body; ValidationException exposes field errors via the Errors property.

Exception Status
InvalidApiTokenException 401
InsufficientBalanceException 402
ExtensionRequiredException 402 (USSD: SetModeAsync to live before renting an extension)
ConflictException 409
ValidationException (.Errors) 422
RateLimitException 429
HellioException other
using Hellio.Messaging;

try
{
    await hellio.SendSmsAsync("233241234567", "Hi");
}
catch (InsufficientBalanceException)
{
    // top up
}
catch (ValidationException ex)
{
    // ex.Errors holds the field-level messages
}

Rate limit: 120 requests/minute per token. A RateLimitException (429) is thrown when you exceed it.

Testing

The client accepts an injected HttpClient, so you can mock the transport in your own tests:

var handler = new YourMockHandler(); // an HttpMessageHandler
var hellio = new HellioClient(token: "test", httpClient: new HttpClient(handler));

See tests/Hellio.Messaging.Tests for a working HttpMessageHandler mock and full coverage.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 37 7/7/2026
1.1.0 68 7/7/2026
1.0.0 87 7/5/2026