Skip to content

DID-7743: [SDK-JAVA] Official Java and Kotlin server SDK: complete API surface, webhooks, Authentication, Transactions, generated types and tests - #1

Merged
arosasg merged 2 commits into
mainfrom
botinc/run-13dc9dbe
Oct 22, 2026
Merged

arosasg merged 2 commits into
mainfrom
botinc/run-13dc9dbe

Conversation

@botinc-agent

@botinc-agent botinc-agent Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Official Java and Kotlin server SDK for Didit (me.didit:didit-java, package me.didit), for DID-7743 under DID-7736. It covers every server API from openapi/ (248 server operations in 47 resources, plus 9 account operations), webhook verification, Didit Authentication, Transactions, Travel Rule and wallet screening. Java 11 bytecode, java.net.http and Jackson, sync methods plus *Async variants, JSpecify nullness for Kotlin.

Nothing is published: version 0.1.0, release workflow off until RELEASES_ENABLED is set, no tag. Merge to main only.

Revision 2: review round 1

Review finding Change
A signature part with a length remainder of 1 threw IllegalArgumentException, also synchronously from verifyAccessTokenAsync Decoded through the guarded helper: TokenVerificationException with malformed, delivered through the future. Tested with 1- and 85-character parts, sync and async
Spec sync read the private repository without credentials, so the head looked empty and every later run failed at the push The guard reads the branch and its commits with the workflow token, under set -euo pipefail; a failed read fails the run
Expired or failed key fetches had no single-flight, so an issuer outage meant one fetch per request Concurrent calls share one discovery fetch and one key fetch. After a failed key fetch, verifications fail fast with that error for 5 seconds. Tested with a held response and with a 503 issuer
verifyIdToken(token, null) skipped the nonce check null now means the token must carry no nonce, as a refreshed ID token does; a token with a nonce is refused
No test that refresh, code exchange or DELETE session/ are not retried; no JWK on another curve Each asserts exactly one request on 500, 503 and a dropped connection; P-384 and curve-less JWKs are skipped
verifyParsed accepted X-Signature-Simple with allowSimple, while DESIGN 9 says it checks V2 only verifyParsed checks X-Signature-V2 only; the backend's Simple-only vector proves it
CONTRIBUTING step 4 implied a rerun picks up a fix on main It now says a rerun builds the tag's commit: rerun only for causes outside the code, otherwise release the next patch
Hardening from the review's notes r and s outside [1, n-1] are refused (CVE-2022-21449 on old JDKs), with a DER signature and r = s = 0 tests. toString() redacts identity data at any depth. An HTTP:// loopback issuer is handled. The cache-lifetime comment matches the code. Tests now count discovery fetches too

Left as is: client_id is sent only when configured, because service-didit-auth makes it optional on token/. jwks_uri may sit on another origin, as OpenID Connect allows; only https is required.

Quickstart against the mock API

./gradlew :examples:quickstartDemo creates a session, reads its decision and verifies a webhook against a local mock of the Didit API:

Session 11111111-2222-4333-8444-555555555555: send the user to https://verify.didit.me/session/synthetic
Decision: Approved
Webhook: session 11111111-2222-4333-8444-555555555555 is now Approved (verified by SIGNATURE_V2)
Mock API saw: [POST /v3/session/ -> 201, GET /v3/session/11111111-2222-4333-8444-555555555555/decision/ -> 200]

Tests

Check Result
./gradlew build --rerun-tasks --no-build-cache (JDK 21): Spotless, javac -Werror with errorprone and NullAway, tests, coverage gate, examples Pass: 437 SDK tests, 6 example tests, 0 failures, 0 skipped
./gradlew :test -PtestJavaVersion=11, =17, =25 437 tests each, 0 failures
Instruction coverage (JaCoCo) 98.0 % overall; core 98.5 %, Authentication 97.9 %, webhooks 96.2 %
./gradlew javadoc publishToMavenLocal Pass; no Javadoc warnings in the SDK
python3 codegen/generate.py --check 584 generated files up to date
Generator tests 19 tests, OK
Contract test 257 SDK methods for 257 operations (248 server and 9 account)
Webhook vectors All 25 pass, including byte-for-byte canonical_v2
gitleaks 8.30.1 with .gitleaks.toml No leaks in the history or the changed files
actionlint 1.7.7 with shellcheck 0.10.0 No findings on all workflows
GitHub CI run 39074701435 on f1a71f6 All 10 jobs pass: Java 11, 17, 21 and 25 on Linux, Java 21 on macOS and Windows, static checks, generated code, examples and Kotlin tests, gitleaks; the live job is skipped without a key

The new tests were also run against the reviewed commit 67d769b. They fail there for the reasons the review gave: IllegalArgumentException, 6 key fetches instead of 1, 3 fetches during an outage instead of 1, the nonce opt-out, names in toString(), and Simple accepted by verifyParsed.

Also checked by hand:

  • Another signer. An ES256 token signed by OpenSSL 3.0 verifies on JDK 11. Its 71-byte DER signature was converted to the 64-byte JOSE form with the Python standard library. Changing the signature gives invalid_signature.

  • Spec-sync guard. I ran the guard and push steps verbatim against a local bare repository with a stubbed gh:

    • No branch: the branch is created.
    • Bot-only branch, pull request open or closed: it is rebuilt with --force-with-lease.
    • A person's commit with the pull request open: left alone.
    • A rejected token: the run fails with exit 128.
    • A concurrent push between the guard and the push: refused, and the person's commit survives.

    The reviewed revision, against a private repository, reads an empty head and its push is rejected as stale.

Public API

Resources: 257 operation methods (each with an Async variant and withRawResponse())
Didit client = Didit.builder().apiKey(...).build()
  .account(): register, verifyEmail, login, resendOtp
    .applications(): list, create, retrieve, update
    .organizations(): list
  .ageEstimation(): estimate
  .aml(): screen
  .amlMonitoring(): retrieveJob
  .billing(): retrieveBalance, topUp
  .biometricTemplates(): list, count, retrieve, delete, batchDelete
  .businessSessions(): listNetworks
  .businesses(): list, retrieve, update, updateStatus, batchDelete, setAmlMonitoring, create, setOngoingMonitoring, retrieveOngoingMonitoringJob, setSessionOngoingMonitoring, retrieveSessionOngoingMonitoringJob, listNetworks
  .cases(): list, create, retrieve, update, delete, assign, resolve, reopen, requestInfo, escalate, transfer, approveResolution, rejectResolution, applyEntityAction, print, listEvents, listAmlHits, retrieveStatistics, bulkUpdateStatus, bulkAssign, listActionReceipts, retryActionReceipt, findDuplicates, listReviewers
    .blueprints(): list, create, retrieve, update, delete, listPresets, installPresets, publish, retrieveDraft, saveDraft, discardDraft, listHistory
    .checklist(): list, toggleItem
    .links(): list, create, delete
    .notes(): list, create, update, delete, retrieveAttachmentUrl, createAttachmentUpload
    .regulatoryReports(): list, create
    .requests(): list, create, cancel, retrieveOptions, retrieveAttachment, preview
    .routingRules(): listHistory, retrieve, update, list, create, publish, retrieveDraft, saveDraft, discardDraft, preview
    .teams(): list, create, retrieve, update
  .customization(): retrieve, update
  .databaseValidation(): validate
  .documentAi(): analyze
  .email(): assessRisk, sendCode, checkCode
  .faceMatch(): compare
  .faceSearch(): search
  .fraudChecks(): retrieve, create, plan
  .idVerification(): verify
  .idWallets(): list, retrieveVerification, pollVerification, cancelVerification, startMitid, startBankidSe, startBankidNo, startVipps, startBuypass, startItsme, startIdin, startFtn, startPersonalausweis, startFrejaid, startUaePass, startGovbr, startOneid, startGovukWallet, startSmartId, startMobileId, startIdcardEe, startEparakstsMobile, startBankidCz, startMojeid, startDiia, startFranceconnect, startAudkenni, startConnectid, startEudi
  .ip(): assessRisk
  .kyb(): search, select, listShareholders, listUbos
  .networks(): list, retrieve, retrieveGraph, listMembers, listSignals, retrieveTimeline, retrieveMap, downloadReport
    .notes(): delete, list, create
  .passiveLiveness(): check
  .phone(): assessRisk, sendCode, checkCode
  .proofOfAddress(): verify
  .questionnaires(): list, create, retrieve, update, delete
  .regulatoryReports(): list, create, retrieve, update, delete, validate, finalize_, retrieveDownloadUrl
  .reportTemplates(): list, create, retrieve, update, delete
  .sessions(): create, retrieveDecision, delete, generatePdf, updateStatus, list, share, importShared, batchDelete, retrieveDeletionJob, updateData, updateFeatureStatus, updateAmlHitStatus, bulkUpdateAmlHitStatus, updatePoaData, updateKybCompanyData, updateKybParty, listNetworks, previewResubmission
    .imports(): create, retrieveTemplate, retrieve, listErrors
    .reviews(): list, create
  .transactions(): list, create, retrieve, createSdkToken, listNetworks, listPartyNetworks
    .rules(): list, create, retrieve, update, delete, backtest, listLibrary, installLibrary, uninstallLibrary
    .travelRule(): updateTransfer, confirmOwnership
  .travelRule(): retrieveSettings, updateSettings, listVasps, registerInbound, createWidgetSession
    .walletAddresses(): list, create, update, delete
  .users(): list, retrieve, update, generatePdf, batchDelete, setAmlMonitoring, updateStatus, create, uploadFace, setOngoingMonitoring, retrieveOngoingMonitoringJob, listNetworks
  .walletScreening(): screen, screenPdf
  .webhooks()
    .destinations(): list, create, retrieve, update, delete
  .workflows(): list, create, retrieve, update, delete

# 257 operation methods (each with an Async variant and withRawResponse())
Hand-written packages
me.didit                 Didit (client and builder)
me.didit.core            ClientOptions, RequestOptions, Page, RawResponse, BinaryResponse, FileUpload,
                         HttpTransport, JdkHttpTransport, Headers, JsonModel, JsonUnion, StringEnum, Version
me.didit.errors          DiditException > ApiStatusException (BadRequest, Authentication, PermissionDenied,
                         NotFound, Conflict, UnprocessableEntity, RateLimit, InternalServer),
                         ApiConnectionException > ApiTimeoutException, ConfigurationException,
                         UnexpectedResponseException, WebhookVerificationException, TokenVerificationException
me.didit.webhooks        Webhooks.verify / verifyParsed / parseEvent / canonicalJson, VerifyOptions, VerifiedBy,
                         WebhookEvent: Session*, User*, Business*, Transaction*, TravelRuleStatusUpdated,
                         ActivityCreated, WorkflowComplianceChanged, KybRegistrySearchResolved, UnknownEvent
me.didit.authentication  Authentication (verifyAccessToken, verifyIdToken, createPkce/State/Nonce,
                         exchangeCode, refresh, getSession, revokeSession, createStepUp, retrieveStepUp),
                         AccessTokenClaims, IdTokenClaims, TokenResponse, AuthenticationSession, StepUp,
                         StepUpParams, PublishableKey, Pkce
me.didit.types           481 generated parameter and response types

One-time setup before the first release

A repository admin does this once, before 2026-11-01 and only with Alex's approval (REL-JAVA DID-7766):

  1. Namespace. Verify me.didit on the Central Portal (central.sonatype.com). It is not on Maven Central today: sdk-android publishes from a GitHub raw Maven repository.
  2. Token. Create a Central Portal user token.
  3. Signing key. Create a GPG signing key and publish its public key on a key server.
  4. Secrets. Add MAVEN_CENTRAL_USERNAME, MAVEN_CENTRAL_PASSWORD, MAVEN_SIGNING_KEY (ASCII-armored private key) and MAVEN_SIGNING_KEY_PASSWORD.
  5. Environment. Create a maven-central environment with required reviewers and "Prevent self-review". Every publish waits for it.
  6. Optional tokens. Add RELEASE_PLEASE_TOKEN and SPEC_SYNC_TOKEN (a GitHub App or fine-grained token), so CI runs on the release and sync pull requests.
  7. Switch on. Set the repository variable RELEASES_ENABLED=true.

After that, merging the release-please pull request creates the tag. The publish job in the same workflow then waits for the environment approval, runs ./gradlew check, signs and uploads through the Central Portal, and attests the jars. CONTRIBUTING.md has the same steps.

Not verified here

  • Live sandbox smoke tests: there is no DIDIT_SANDBOX_API_KEY here. Run DIDIT_SANDBOX_API_KEY=... ./gradlew liveTest.
  • Authentication against a real issuer: point AuthenticationExample at an application's issuer.
  • The release and spec-sync workflows on GitHub itself: the first real runs come after RELEASES_ENABLED is set, and on the next daily schedule after merge.

Parity

The DESIGN.md questions this raised for all seven SDKs, and the parity checks for the other repositories, are in the "Follow-ups for the supervisor" comment on DID-7743. docs/DESIGN.md and openapi/ are unchanged.

Opened by a BotInc run. Delivery follows the workflow's review and merge policy.

Requested by Alejandro Rosas in DID-7743.

Full diff

🤖 Generated with Claude Code

…I surface, webhooks, Authentication, Transactions, generated types and tests

Opened by a BotInc run. Delivery follows the workflow's review and merge policy.
…I surface, webhooks, Authentication, Transactions, generated types and tests

Opened by a BotInc run. Delivery follows the workflow's review and merge policy.
@arosasg
arosasg merged commit 351c94e into main Oct 22, 2026
11 checks passed
@arosasg
arosasg deleted the botinc/run-13dc9dbe branch October 22, 2026 20:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant