DocumentationAPI v1 · 1.3.0

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
BuilderPropriété SpringDéfaut
.apiKey()labyrinthe.api-keyLABYRINTHE_API_KEY
.baseUrl()labyrinthe.base-urlProduction
.timeout()labyrinthe.timeout30s
.maxRetries() / .maxRetryWait()labyrinthe.max-retries / labyrinthe.max-retry-wait2 / 10s
.defaultCountryCode(), .defaultSender()labyrinthe.default-country-code, labyrinthe.default-sender243, —
—labyrinthe.webhook.enabled, .path, .secret, .tolerancetrue, /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#

Java
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()); // QUEUED

Plusieurs destinataires (jusqu'à 1000, tout ou rien) :

Java
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 :

Java
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 */ }
Java
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");
Java
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#

1. Envoyer le code
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");
}
2. Vérifier le code
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#

Java
// 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.

Java
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.