Packages
SDK Java
Installer cd.labyrinthe:labyrinthe-connect et le starter Spring Boot : SMS, OTP, webhooks et erreurs en Java 11+.
Client Java 11+ sans dépendance (java.net.http), synchrone avec une variante asynchrone (client.async(), CompletableFuture). Le starter Spring Boot 3 (Java 17+) configure le client et un contrôleur de webhooks.
Version 1.2.0. Code source, exemples et journal des modifications : dossier java sur GitHub.
Installation#
<dependency>
<groupId>cd.labyrinthe</groupId>
<artifactId>labyrinthe-connect</artifactId>
<version>1.2.0</version>
</dependency>
<!-- Spring Boot 3 -->
<dependency>
<groupId>cd.labyrinthe</groupId>
<artifactId>labyrinthe-connect-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>Configuration#
import cd.labyrinthe.connect.LabyrintheClient;
import java.time.Duration;
// From the environment: LABYRINTHE_API_KEY, LABYRINTHE_BASE_URL
LabyrintheClient client = LabyrintheClient.fromEnv();
// Or with the builder
LabyrintheClient configured = LabyrintheClient.builder()
.apiKey("lsk_test_xxxxxxxxxxxxxxxx")
.timeout(Duration.ofSeconds(30))
.maxRetries(2)
.defaultSender("MABOUTIQUE")
.build();
configured.isTestMode(); // true with a lsk_test_ key| Builder | Propriété Spring | Défaut |
|---|---|---|
.apiKey() | labyrinthe.api-key | LABYRINTHE_API_KEY |
.baseUrl() | labyrinthe.base-url | Production |
.timeout() | labyrinthe.timeout | 30s |
.maxRetries() / .maxRetryWait() | labyrinthe.max-retries / labyrinthe.max-retry-wait | 2 / 10s |
.defaultCountryCode(), .defaultSender() | labyrinthe.default-country-code, labyrinthe.default-sender | 243, — |
| — | labyrinthe.webhook.enabled, .path, .secret, .tolerance | true, /webhooks/labyrinthe, —, 5m |
Chaque méthode a une surcharge avec un dernier argument RequestOptions (RequestOptions.requestId("…"), .timeout(…), onResponse). Les champs facultatifs des résultats sont des Optional.
Envoyer un SMS#
import cd.labyrinthe.connect.LabyrintheClient;
import cd.labyrinthe.connect.Message;
import cd.labyrinthe.connect.SendSmsParams;
LabyrintheClient client = LabyrintheClient.builder()
.apiKey("lsk_test_xxxxxxxxxxxxxxxx") // or LabyrintheClient.fromEnv() (LABYRINTHE_API_KEY)
.build();
Message message = client.sms().send(SendSmsParams.builder()
.to("243812345678")
.body("Votre commande 1042 est prête.")
.reference("CMD-1042")
.idempotencyKey("order-1042-ready") // replaying within 24 h sends nothing again
.build());
System.out.println(message.id() + " " + message.status()); // QUEUEDPlusieurs destinataires (jusqu'à 1000, tout ou rien) :
import cd.labyrinthe.connect.LabyrintheClient;
import cd.labyrinthe.connect.Message;
import cd.labyrinthe.connect.MessageBatch;
import cd.labyrinthe.connect.SendManySmsParams;
import java.util.List;
LabyrintheClient client = LabyrintheClient.builder()
.apiKey("lsk_test_xxxxxxxxxxxxxxxx") // or LabyrintheClient.fromEnv() (LABYRINTHE_API_KEY)
.build();
MessageBatch batch = client.sms().sendMany(SendManySmsParams.builder()
.recipients(List.of("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")
.build());
for (Message message : batch) {
System.out.println(message.recipient() + " " + message.status());
}Estimer le coût, consulter le solde, lister les messages :
import cd.labyrinthe.connect.Estimate;
import cd.labyrinthe.connect.EstimateSmsParams;
import cd.labyrinthe.connect.LabyrintheClient;
import java.util.List;
LabyrintheClient client = LabyrintheClient.builder()
.apiKey("lsk_test_xxxxxxxxxxxxxxxx") // or LabyrintheClient.fromEnv() (LABYRINTHE_API_KEY)
.build();
Estimate estimate = client.sms().estimate(EstimateSmsParams.builder()
.body("Votre code est 482913")
.recipients(List.of("243812345678", "0812345678", "0899999999"))
.build());
System.out.println(estimate.recipientsCount() + " recipients, " + estimate.totalCredits() + " credits");
if (!estimate.isAffordable()) { /* top up before sending */ }import cd.labyrinthe.connect.Balance;
import cd.labyrinthe.connect.LabyrintheClient;
LabyrintheClient client = LabyrintheClient.builder()
.apiKey("lsk_test_xxxxxxxxxxxxxxxx") // or LabyrintheClient.fromEnv() (LABYRINTHE_API_KEY)
.build();
Balance balance = client.balance().get();
System.out.println(balance.balance() + " credits available, " + balance.held() + " held");import cd.labyrinthe.connect.LabyrintheClient;
import cd.labyrinthe.connect.ListSmsParams;
import cd.labyrinthe.connect.Message;
import cd.labyrinthe.connect.MessageStatus;
import java.time.LocalDate;
LabyrintheClient client = LabyrintheClient.builder()
.apiKey("lsk_test_xxxxxxxxxxxxxxxx") // or LabyrintheClient.fromEnv() (LABYRINTHE_API_KEY)
.build();
ListSmsParams params = ListSmsParams.builder()
.status(MessageStatus.DELIVERED)
.from(LocalDate.of(2026, 11, 1))
.perPage(50)
.build();
for (Message message : client.sms().list(params).autoPagingIterable()) {
System.out.println(message.id() + " " + message.recipient());
}Vérification OTP#
import cd.labyrinthe.connect.LabyrintheClient;
import cd.labyrinthe.connect.OtpLocale;
import cd.labyrinthe.connect.OtpSendResult;
import cd.labyrinthe.connect.SendOtpParams;
import cd.labyrinthe.connect.exception.OtpResendTooSoonException;
import java.time.Duration;
LabyrintheClient client = LabyrintheClient.builder()
.apiKey("lsk_test_xxxxxxxxxxxxxxxx") // or LabyrintheClient.fromEnv() (LABYRINTHE_API_KEY)
.build();
try {
OtpSendResult otp = client.otp().send(SendOtpParams.builder()
.phone("243812345678")
.locale(OtpLocale.FR)
.build());
session.setAttribute("otp_id", otp.otpId()); // keep it server-side
otp.testCode().ifPresent(code -> System.out.println("test code: " + code));
} catch (OtpResendTooSoonException e) {
long seconds = e.retryAfter().map(Duration::getSeconds).orElse(60L);
System.out.println("Wait " + seconds + " s");
}import cd.labyrinthe.connect.LabyrintheClient;
import cd.labyrinthe.connect.OtpFailureReason;
import cd.labyrinthe.connect.OtpVerifyResult;
LabyrintheClient client = LabyrintheClient.builder()
.apiKey("lsk_test_xxxxxxxxxxxxxxxx") // or LabyrintheClient.fromEnv() (LABYRINTHE_API_KEY)
.build();
OtpVerifyResult result = client.otp().verify(otpId, typedCode);
if (result.isVerified()) {
// phone verified: log the user in
} else if (result.reason().orElse(null) == OtpFailureReason.INVALID_CODE) {
System.out.println("Wrong code, " + result.attemptsRemaining().orElse(0) + " attempt(s) left");
} else {
System.out.println("Ask for a new code: " + result.reason().orElse(null));
}Recevoir les webhooks#
// application.properties (Spring Boot starter):
// labyrinthe.api-key=${LABYRINTHE_API_KEY}
// labyrinthe.webhook.secret=${LABYRINTHE_WEBHOOK_SECRET}
// labyrinthe.webhook.path=/webhooks/labyrinthe
import cd.labyrinthe.connect.webhook.MessageStatusChanged;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
class LabyrintheEvents {
@EventListener
void onStatus(MessageStatusChanged event) {
System.out.println(event.data().messageId() + " is now " + event.data().status());
}
}
// Without Spring:
// WebhookEvent event = Webhooks.constructEvent(rawBody, request.getHeader("X-Signature"), secret);Erreurs et maintenance#
La maintenance (503 FEATURE_DISABLED et OTP_SERVICE_DISABLED) a sa propre exception : rien n'a été envoyé ni débité. Le package relance seul si Retry-After est court (10 s par défaut) ; sinon, mettez l'envoi en file d'attente. Voir Erreurs.
import cd.labyrinthe.connect.LabyrintheClient;
import cd.labyrinthe.connect.exception.ApiException;
import cd.labyrinthe.connect.exception.FeatureDisabledException;
import cd.labyrinthe.connect.exception.InsufficientCreditsException;
import cd.labyrinthe.connect.exception.RateLimitException;
import java.time.Duration;
LabyrintheClient client = LabyrintheClient.builder()
.apiKey("lsk_test_xxxxxxxxxxxxxxxx") // or LabyrintheClient.fromEnv() (LABYRINTHE_API_KEY)
.build();
try {
client.sms().send("243812345678", "Bonjour");
} catch (FeatureDisabledException e) {
// 503 maintenance: nothing was sent, no credit used
System.out.println(e.feature().orElse("") + " " + e.blockedBy().orElse("") + " " + e.adminMessage().orElse(""));
e.retryAfter().ifPresentOrElse(this::scheduleRetry, this::queueForLater);
} catch (InsufficientCreditsException e) {
notifyTopUp(e.available().orElse(0), e.required().orElse(0));
} catch (RateLimitException e) {
scheduleRetry(e.retryAfter().orElse(Duration.ofMinutes(1)));
} catch (ApiException e) {
System.err.println(e.code() + " " + e.statusCode() + " " + e.requestId().orElse(""));
}Mode test#
client.isTestMode() indique une clé lsk_test_… ; otp.testCode() renvoie alors un Optional contenant le code. Webhooks.sign(rawBody, secret) fabrique une signature valide pour vos tests.