Packages
.NET SDK
Install Labyrinthe.Connect and Labyrinthe.Connect.AspNetCore: async client, dependency injection, webhooks and errors.
Two NuGet packages: Labyrinthe.Connect (.NET Standard 2.0 and .NET 8) for the client, and Labyrinthe.Connect.AspNetCore (.NET 8) for dependency injection and webhooks. Every method is async and accepts a CancellationToken.
Version 1.2.0. Source code, examples and changelog: dotnet folder on GitHub.
Installation#
dotnet add package Labyrinthe.Connect
dotnet add package Labyrinthe.Connect.AspNetCore # ASP.NET Core appsConfiguration#
using Labyrinthe.Connect;
using Labyrinthe.Connect.AspNetCore;
// Console app: LABYRINTHE_API_KEY and LABYRINTHE_BASE_URL from the environment
using var client = new LabyrintheClient();
// ASP.NET Core: typed HttpClient registered in DI, configuration section "Labyrinthe"
builder.Services.AddLabyrinthe(builder.Configuration);
// or: builder.Services.AddLabyrinthe(options => options.ApiKey = "lsk_test_xxxxxxxxxxxxxxxx");
// Then inject LabyrintheClient into your services
public sealed class OrderNotifier(LabyrintheClient labyrinthe) { /* … */ }| Option | Default | Role |
|---|---|---|
ApiKey | LABYRINTHE_API_KEY | Required (Labyrinthe__ApiKey variable in configuration). |
BaseUrl | LABYRINTHE_BASE_URL, puis production | A bare host gets /api/public/v1. |
Timeout | 30 s | Per attempt. |
MaxRetries / MaxRetryWait | 2 / 10 s | Retries and longest accepted Retry-After wait. |
DefaultCountryCode, DefaultSender | 243, — | Local numbers, default Sender ID. |
WebhookSecret, WebhookTolerance | LABYRINTHE_WEBHOOK_SECRET, 5 min | Webhook endpoint. |
Mind the names: the method is AddLabyrinthe and the balance is read from Balance.Available. client.WithRequestId("…") returns a client that sends this X-Request-Id.
Send an SMS#
using Labyrinthe.Connect;
using var client = new LabyrintheClient(new LabyrintheClientOptions
{
ApiKey = "lsk_test_xxxxxxxxxxxxxxxx", // or new LabyrintheClient() (LABYRINTHE_API_KEY)
});
var message = await client.Sms.SendAsync(new SendSmsRequest
{
To = "243812345678",
Body = "Votre commande 1042 est prête.",
Reference = "CMD-1042",
IdempotencyKey = "order-1042-ready", // replaying within 24 h sends nothing again
});
Console.WriteLine($"{message.Id} {message.Status}"); // QueuedSeveral recipients (up to 1000, all or nothing):
using Labyrinthe.Connect;
using var client = new LabyrintheClient(new LabyrintheClientOptions
{
ApiKey = "lsk_test_xxxxxxxxxxxxxxxx", // or new LabyrintheClient() (LABYRINTHE_API_KEY)
});
var batch = await client.Sms.SendManyAsync(new SendManySmsRequest
{
Recipients = new[] { "243812345678", "0899999999" },
Body = "Rappel : reunion demain 9h.",
Sender = "MABOUTIQUE", // an approved Sender ID of the account
Reference = "RAPPEL-2026-11-06",
IdempotencyKey = "reminder-2026-11-06",
});
foreach (var message in batch.Messages)
{
Console.WriteLine($"{message.Recipient} {message.Status}");
}Estimate the cost, read the balance, list messages:
using Labyrinthe.Connect;
using var client = new LabyrintheClient(new LabyrintheClientOptions
{
ApiKey = "lsk_test_xxxxxxxxxxxxxxxx", // or new LabyrintheClient() (LABYRINTHE_API_KEY)
});
var estimate = await client.Sms.EstimateAsync(new EstimateSmsRequest
{
Body = "Votre code est 482913",
Recipients = new[] { "243812345678", "0812345678", "0899999999" },
});
Console.WriteLine($"{estimate.Encoding} {estimate.RecipientsCount} {estimate.TotalCredits}");
if (!estimate.IsAffordable) { /* top up before sending */ }using Labyrinthe.Connect;
using var client = new LabyrintheClient(new LabyrintheClientOptions
{
ApiKey = "lsk_test_xxxxxxxxxxxxxxxx", // or new LabyrintheClient() (LABYRINTHE_API_KEY)
});
var balance = await client.Balance.GetAsync();
Console.WriteLine($"{balance.Available} credits available, {balance.Held} held");using Labyrinthe.Connect;
using var client = new LabyrintheClient(new LabyrintheClientOptions
{
ApiKey = "lsk_test_xxxxxxxxxxxxxxxx", // or new LabyrintheClient() (LABYRINTHE_API_KEY)
});
var options = new ListSmsOptions
{
Status = MessageStatus.Delivered,
From = DateFilter.Day(2026, 11, 1),
PerPage = 50,
};
await foreach (var message in client.Sms.ListAutoPagingAsync(options))
{
Console.WriteLine($"{message.Id} {message.Recipient}");
}OTP verification#
using Labyrinthe.Connect;
using var client = new LabyrintheClient(new LabyrintheClientOptions
{
ApiKey = "lsk_test_xxxxxxxxxxxxxxxx", // or new LabyrintheClient() (LABYRINTHE_API_KEY)
});
try
{
var otp = await client.Otp.SendAsync(new SendOtpRequest
{
Phone = "243812345678",
Locale = OtpLocale.Fr,
});
HttpContext.Session.SetString("otp_id", otp.OtpId); // keep it server-side
Console.WriteLine(otp.TestCode); // "482913" with a lsk_test_ key, null in live
}
catch (OtpResendTooSoonException error)
{
Console.WriteLine($"Wait {error.RetryAfter?.TotalSeconds} s");
}using Labyrinthe.Connect;
using var client = new LabyrintheClient(new LabyrintheClientOptions
{
ApiKey = "lsk_test_xxxxxxxxxxxxxxxx", // or new LabyrintheClient() (LABYRINTHE_API_KEY)
});
var result = await client.Otp.VerifyAsync(otpId, code);
if (result.Verified)
{
// phone verified: log the user in
}
else if (result.Reason == OtpFailureReason.InvalidCode)
{
Console.WriteLine($"Wrong code, {result.AttemptsRemaining} attempt(s) left");
}
else
{
Console.WriteLine($"Ask for a new code: {result.Reason}");
}Receive webhooks#
using Labyrinthe.Connect;
using Labyrinthe.Connect.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// appsettings.json: { "Labyrinthe": { "ApiKey": "lsk_test_…", "WebhookSecret": "…" } }
builder.Services.AddLabyrinthe(builder.Configuration);
var app = builder.Build();
app.MapLabyrintheWebhooks("/webhooks/labyrinthe", (webhookEvent, cancellationToken) =>
{
switch (webhookEvent)
{
case MessageStatusChanged changed:
Console.WriteLine($"{changed.Data.MessageId} {changed.Data.Status.ToApiString()}");
break;
case BalanceLow low:
Console.WriteLine($"Low balance: {low.Data.Balance} / {low.Data.Threshold}");
break;
}
return Task.CompletedTask;
});
app.Run();Errors and maintenance#
Maintenance (503 FEATURE_DISABLED and OTP_SERVICE_DISABLED) has its own exception: nothing was sent or debited. The package retries on its own if Retry-After is short (10 s by default); otherwise, queue the send. See Errors.
using Labyrinthe.Connect;
using var client = new LabyrintheClient(new LabyrintheClientOptions
{
ApiKey = "lsk_test_xxxxxxxxxxxxxxxx", // or new LabyrintheClient() (LABYRINTHE_API_KEY)
});
try
{
await client.Sms.SendAsync("243812345678", "Bonjour");
}
catch (FeatureDisabledException error)
{
// 503 maintenance: nothing was sent, no credit used
Console.WriteLine($"{error.Feature} {error.BlockedBy} {error.AdminMessage} {error.Until}");
if (error.RetryAfter is { } wait) ScheduleRetry(wait);
else QueueForLater();
}
catch (InsufficientCreditsException error)
{
NotifyTopUp(error.Available, error.Required);
}
catch (RecipientNotSendableException error)
{
foreach (var rejected in error.Rejected) Console.WriteLine($"{rejected.Recipient} {rejected.Reason}");
}
catch (RateLimitException error)
{
ScheduleRetry(error.RetryAfter ?? TimeSpan.FromMinutes(1));
}
catch (ApiException error)
{
Console.Error.WriteLine($"{error.Code} {error.StatusCode} {error.RequestId}");
}Test mode#
client.IsTestMode tells a lsk_test_… key; otp.TestCode then holds the code (hidden from ToString()). Webhook.Sign(rawBody, secret) builds a valid signature for your tests.