Integrations
Java

Java SDK

com.lunixi:lunixi-java-sdk is the Java client for Lunixi. Request signing, the token exchange, reading the response envelope and cursor paging live in the SDK.

Java17 or newer
Runtime dependenciesnone
LicenseMIT

Scope

ComponentPurpose
LunixiConfigImmutable configuration, validated when built
auth.Ed25519KeyPair · Ed25519SignerKey pair generation, request signing
auth.TokenManager · TokenStoreSigned token exchange and token cache
http.HttpTransport · JdkHttpTransportSwappable HTTP transport
fraud.FraudClientFraud decisions, decision records and event submission
webhook.WebhookVerifierFail-closed verification of incoming webhooks
exception.*Typed error family

In this version the Java SDK covers the fraud surface and webhook verification. For payments and the other products you can use REST without an SDK; the request layer is the same.

Installation

Gradle:

implementation 'com.lunixi:lunixi-java-sdk:0.1.0'

Maven:

<dependency>
  <groupId>com.lunixi</groupId>
  <artifactId>lunixi-java-sdk</artifactId>
  <version>0.1.0</version>
</dependency>

The SDK has no runtime dependencies: signing, HMAC, HTTP and JSON come from the JDK, so it causes no version conflicts with the libraries on your classpath.

Key pair

Ed25519KeyPair keyPair = Ed25519KeyPair.generate();
// keyPair.publicKeyPem()  → upload it in the dashboard
// keyPair.privateKeyPem() → keep it in your secret manager

Do not write the private key into source code and do not let it reach your logs.

Creating the client

LunixiClient client = LunixiClient.create(LunixiConfig.builder()
        .baseUrl("https://api-gateway.lunixi.com")
        .keyId(System.getenv("LUNIXI_KEY_ID"))
        .privateKey(System.getenv("LUNIXI_PRIVATE_KEY"))
        .environment(LunixiConfig.Environment.TEST)
        .build());

Build the client once and keep it. It is immutable, can be shared between threads and carries the token cache. The builder also takes timeout(Duration), maxRetries(int), transport(HttpTransport) and tokenStore(TokenStore).

Fraud decision

FraudDecision decision = client.fraud().evaluate(EvaluateRequest.create()
        .standaloneIntegrationMode()
        .channel(FraudTaxonomy.Channel.STANDALONE_API)
        .productContext(FraudTaxonomy.ProductContext.DIRECT_API)
        .transactionRef("order-2026-0001")
        .payment(Map.of("amount", 25000, "currency", "TRY"))
        .customer(Map.of("email", email, "country", "TR"))
        .device(Map.of("ipAddress", ipAddress)));
 
if (decision.isBlock()) {
    // decline the transaction
} else if (decision.isForce3d()) {
    // retry with 3D Secure
}

For a payment taken through Lunixi the decision is already made during authorization; read it from its record instead of asking again:

Optional<FraudDecision> recorded = client.fraud().decisions().latestForPayment(paymentId);

evaluate is for screening your own events (sign-up, login, withdrawal, a payment you authorize with your own provider). → Fraud

Decision records

for (FraudDecision record : client.fraud().decisions()
        .iterate(DecisionLogFilter.create().limit(100))) {
    // pages are fetched by following the cursor
}

The cursor is opaque: do not decode, build or change it; send it back as is with the next request.

Event submission

client.fraud().events().record(Map.of(
        "eventId", "evt-0001",
        "eventType", "card_payment",
        "subjectType", "customer",
        "occurredAt", "2026-10-11T08:00:00Z"));
 
FraudEventBatchResult batch = client.fraud().events().recordBatch(events);
if (!batch.failures().isEmpty()) {
    // fix the rejected events and send them again with the same eventId
}

A batch carries at most 500 events; a larger batch is refused before it goes on the network. A batch can succeed partly: the endpoint answers 200 and the rejected events are in failures(). Sending again with the same eventId is safe.

Webhook verification

WebhookEvent event = new WebhookVerifier().verify(rawBody, headers, endpointSecret);

rawBody must be the raw body; parsing the JSON and serialising it again changes the bytes and the delivery cannot be verified. Verification is fail-closed and throws on every mismatch. The fraud event types are constants in FraudEvents.

Errors

ClassWhen
ValidationException400 / 422 — the body was rejected
AuthenticationExceptionNo token could be obtained (signature, key, IP)
RateLimitException429 — retryAfter()
ServiceUnavailableException503 — retryAfter()
LunixiApiExceptionOther error answers and transport errors
ConfigurationExceptionLocal validation, before anything is sent
SignatureExceptionThe key could not be read or used for signing
WebhookVerificationExceptionThe webhook could not be verified

Branch on code(); the message text can change.

Retries

  • 401: the token is renewed and the request is sent once more.
  • Transport error or 5xx: only reads and calls carrying an idempotency key are retried.
  • Other 4xx: never retried.

Related