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.
| Java | 17 or newer |
| Runtime dependencies | none |
| License | MIT |
Scope
| Component | Purpose |
|---|---|
LunixiConfig | Immutable configuration, validated when built |
auth.Ed25519KeyPair · Ed25519Signer | Key pair generation, request signing |
auth.TokenManager · TokenStore | Signed token exchange and token cache |
http.HttpTransport · JdkHttpTransport | Swappable HTTP transport |
fraud.FraudClient | Fraud decisions, decision records and event submission |
webhook.WebhookVerifier | Fail-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 managerDo 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
| Class | When |
|---|---|
ValidationException | 400 / 422 — the body was rejected |
AuthenticationException | No token could be obtained (signature, key, IP) |
RateLimitException | 429 — retryAfter() |
ServiceUnavailableException | 503 — retryAfter() |
LunixiApiException | Other error answers and transport errors |
ConfigurationException | Local validation, before anything is sent |
SignatureException | The key could not be read or used for signing |
WebhookVerificationException | The 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
- Fraud — decision model and event submission
- Authentication — key, signature and token lifetime
- REST without an SDK — endpoints the SDK does not cover