All notable changes to com.assinafy:assinafy-sdk will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
-
WebhookVerifier,AssinafyClient.webhookVerifier(), andAssinafyClientOptions.webhookSecret. Assinafy publishes no webhook signature header, no signing scheme, and nowhere on the subscription to register a shared secret, so there is nothing in a delivery for a client library to verify. The class implemented a conventional HMAC-SHA256-over-raw-body check against a secret the platform never issues, which invited callers to treat afalseresult as evidence of forgery when it only ever meant "no secret configured". Authenticate deliveries at a trusted network boundary instead, and re-read the affected entity through the API before acting on it.Migration: deserialize the delivery body into
WebhookPayloadwith your own Jackson mapper (FAIL_ON_UNKNOWN_PROPERTIES=false), then readgetEvent(),getSubject(), andgetObject()directly.WebhookPayloadis unchanged.
Supersedes the unreleased 1.5.2 source; every change below is relative to the published 1.5.1
artifact. One Java type now models each API resource, signer/assignment placement rules are
single-sourced, request fields the API never accepted are gone, and the release pipeline publishes
signed build provenance. 299 unit tests pass and the opt-in LiveApiSmokeIT runs 25 flows.
DocumentListItem,DocumentUploadResponse, andDocumentDetailsare replaced by a singleDocument. The API returns one document schema from upload, list, search, get, rename, and create-from-template, so the SDK now returns one type from all of them.Documentcarries the union of the previous three field sets; a field a given response does not populate isnull. Migration: replace all three type names withDocument.Document.getAssignment()is typedAssignmenteverywhere, soDocumentUploadResponse.getAssignmentDetails()is gone — callgetAssignment().WorkspaceListItemis replaced byWorkspace;TemplateListItemis replaced byTemplate. Each pair carried identical fields.workspaces().list()now returnsPaginatedResult<Workspace>andtemplates().list()returnsPaginatedResult<Template>.DocumentResource.confirmSignerData(...), deprecated in 1.5.1, is removed. Usesigners().confirmSignerData(...), which returns the server-normalizedSigner.CreateSignerRequest.metadataandUploadAndRequestSignaturesRequest.SignerEntry.metadata. Signer creation has no metadata field, so these values were accepted and then dropped. Removing them turns a silent no-op into a compile error.Signer.metadata. The signer payload has no metadata field, so the accessor always returnednull.AssignmentSigner.getNotificationHistoryEntries()/setNotificationHistoryEntries(...).getNotificationHistory()is now typedList<NotificationHistoryEntry>directly, so the converting accessors have no purpose. Migration: renamegetNotificationHistoryEntries()togetNotificationHistory().
signers().create(...)always issues the create request. In 1.5.1 it silently returned an existing signer when the email matched. Usesigners().findOrCreate(...)for the reusing behavior; it returns an exact case-insensitive email match unchanged.publicDocuments().getBasicInfo(...)returnsDocumentinstead ofMap<String,Object>.users().get()accepts both the{user, accounts}and the flat user response shapes.- Statistics field names follow the API:
signature_requests_notification_email,..._notification_whatsapp,..._notification_bypass,..._verification_email,..._verification_whatsapp,..._verification_bypass, and..._verification_digital_certificate, plussignature_requests_viewed. The formergetSignatureRequestsEmail()andgetSignatureRequestsWhatsapp()remain as deprecated aliases. - A supplied
CreateSignerRequest.cpfis persisted through the signergovernment_idupdate after creation; if that update fails, the new signer is deleted. uploadAndRequestSignatures(...)reconciles an indeterminate assignment or signer create response before rolling back, and retains resources whose outcome cannot be established rather than deleting a request that may already have been dispatched.
documents().appendTagIds(...)andreplaceTagIds(...)resolve workspace tag IDs before changing a document.appendTags(...)andreplaceTags(...)continue to take tag names.signers().findOrCreate(...)— reuse-by-email creation, split out ofcreate.authentication().changePasswordResult(...),requestPasswordResetResult(...), andresetPasswordResult(...)return the response payload; thevoidforms remain.AssignmentMethodis the source of the acceptedmethodvocabulary, so the enum and the validation cannot drift apart.
- Signer-facing assignment calls (
sign,decline,getForSigner) build thesigner-access-codequery through the same helper as every other signer route, so encoding is identical everywhere. - Assignment and template signer placement share one implementation of the delivery-method
vocabulary and the signing-order rules (all-or-nothing steps, contiguous from 1, a
DigitalCertificatesigner alone in its step), removing two divergent copies.
- Releases publish to GitHub Packages with
actions/attest-build-provenanceand create a GitHub release carrying the jars, so every published artifact has verifiable provenance. - The release job runs only after the verification job succeeds; pull-request runs supersede each other while pushes, tags, and the scheduled sandbox run always finish.
README.mdis reorganized as a single read-through guide, anddocs/API_REFERENCE.mddocuments every operation with its full request and response payload.
- Tagged releases publish the jar, sources, and Javadoc to GitHub Packages.
README.mddocuments the~/.m2/settings.xmlcredentials and the repository entry consumers need. - The release job verifies that the pushed tag matches the project version before deploying.
Fixes three non-functional signer/public endpoints, fills the remaining documented endpoint gaps,
and adds request/response payload documentation. 195 unit tests pass; the opt-in LiveApiSmokeIT
runs 18 flows.
SignerResource.acceptTermsput the access code in the JSON body and PUT to a bare path, so the endpoint (which authenticates via thesigner-access-codequery parameter) rejected the call with 400 "access-code parameter missing". It now sends the code as the query parameter with no body. Return type changed fromSignertovoid(the 200 response carries nodata).SignerResource.verifyEmaillikewise put the access code in the body. It now sendssigner-access-codeas the query parameter and onlyverification-codein the body.PublicDocumentResource.sendTokensent{recipient, channel}; the documented body is a singleemailfield. New signaturesendToken(documentId, email); the oldsendToken(documentId, recipient, channel)is retained as@Deprecated(delegates, channel ignored).SignerResource.createduplicate-email recovery only caught HTTP 409, but the live API returns 400 for a duplicate email. The fallback now re-queries by email on any 4xx and returns the existing signer, keepingcreate()idempotent.DocumentResource.download(id, artifact)now URL-encodes the artifact name (consistent with the signer-scoped download).FieldResource.validate/validateMultiplenow reuse the sharedwithAccessCodehelper.
DocumentResource.rename(documentId, name)—PATCH /documents/{id}(rename).DocumentResource.search(ListParams)—GET /accounts/{id}/documents/search(lightweight compact search).SignerResource.searchDocuments(signerId, accessCode, search)—GET /signers/{signerId}/documents/search.AssignmentResource.list(ListParams)—GET /assignments. Documented caveat: the endpoint resolves the account from an interactive session and is not available to API-key clients (returns 400 "account context required"); use with a session/Bearer token.WorkspaceResource.getTheme/downloadLogo/uploadLogo/deleteLogo— the Accountsthemeandlogooperations, plus a newAccountThememodel.WorkspaceResource.delete(accountId, force)— sends the documented{"force": true}body to cancel a blocking paid subscription and delete immediately.SignerResource.uploadSignature(code, type, image, reuse)— the documentedreusequery parameter (setsis_signature_reusable);typeis now optional per the docs.notification_sender_typeonCreateWorkspaceRequest/UpdateWorkspaceRequestand theWorkspacemodel (plusroles,is_delete_allowed);is_signature_reusableonSigner.- Transport:
patch,delete(path, body)andpostFile(single-file multipart) onApiHttpClient/OkHttpApiClient.
SignerResource.confirmSignerDatanow returns the server-normalisedSigner(wasvoid); its Javadoc lists the documented body fields (full_name,email,government_id).PublicDocumentResource.getBasicInfonow returns the typedDocumentDetails(wasMap<String,Object>) — the payload is the same document shape asdocuments().details().AssinafyClient.uploadAndRequestSignatures,WebhookResource.register, theAssignmentResourceandFieldResourceCRUD methods, and the new methods above all gained Javadoc conveying their route, defaults and request/response contract.
Live behavior is authoritative in these places; the SDK follows it:
- Document tag attach/replace take tag names (auto-created), not IDs. The API reference labels
the
tagsarray "Tag IDs", but attaching by name links the existing tag while an ID string creates a tag named after the ID.appendTags/replaceTagscorrectly send names; Javadoc now notes the discrepancy. - Field create/update accept more than the docs list:
is_activeis honored on create, andtype/is_requiredare mutable on update. The DTO fields were kept. - Assignment cost estimation requires at least one signer (server-side) even for
collect. - Deleting a tag still attached to a document returns 409 unless
force=true— the Javadoc claim is accurate.
- Bumped
actions/checkouttov7(setup-java@v5,upload-artifact@v7already current); build targets Java 25 (current LTS). - README: corrected the changed method examples, documented the new endpoints, and added a Request / Response Payloads section with real wire shapes for the core operations.
A non-breaking release focused on correctness, robustness, and test coverage.
175 unit tests pass, now including wire-level OkHttpApiClient tests backed by
MockWebServer; the opt-in LiveApiSmokeIT runs 16 flows and also exercises
binary downloads.
- Binary downloads no longer return the error body as file bytes.
OkHttpApiClient.getBinaryignored the HTTP status, so a 4xx/5xx (e.g. downloading thecertificatedartifact of an unsigned document, or a missing document) handed the JSON error envelope back as if it were the PDF/JPEG. It now throwsApiException(with the server message) on any non-2xx, sodocuments().download/thumbnail/downloadPageandsigners().downloadSignature/downloadDocumentfail loudly instead of producing a corrupt file. Verified against the live sandbox. ResponseHandler.handleVoidnow also honors an in-body error envelope ({status,…}) on an HTTP 200, consistent with the typed/list/map handlers.WebhookVerifier.verify(String, String)returnsfalse(fail-closed) on anullpayload instead of throwing aNullPointerException.BaseResource.serialisefailures now throwAssinafyExceptioninstead of a rawRuntimeException, so every SDK error is catchable asAssinafyException.
AuthenticationException(401/403) andRateLimitException(429), subtypes ofApiException—ApiException.fromResponsereturns the most specific type so callers can react to auth/rate-limit without switching on the status code. Existingcatch (ApiException)handlers are unaffected.DocumentArtifacts.thumbnail— the inline thumbnail URL the API returns on every document (previously silently dropped); reachable viagetArtifacts().getThumbnail()with no extra round-trip.RenameTagRequest.builder().clearColor()— sends an explicit"color": nullso a tag's colour can be cleared (the documented tri-state); a plain builder still leaves the colour unchanged.WebhookPayload.getPayloadRaw()— the raw payload exactly as delivered (object, array, or null).WebhookDispatch.resourcefield.
- The response
ObjectMappernow enablesACCEPT_EMPTY_STRING_AS_NULL_OBJECT, so a typed-object field returned as""(e.g. an activityorigin) coerces tonullrather than failing the whole parse. WebhookPayload.payloadis now stored untyped internally so an empty-array payload ([], delivered by some event types) no longer breaksextractEvent.getPayload()still returnsMap<String,Object>(object-shaped, elsenull) — no source change for callers.WebhookVerifierJavadoc and the README webhook section now state plainly that the platform does not document a signature scheme, and thatverify() == falsedoes not by itself indicate forgery.FieldResource.createvalidates the documented-requiredtype/nameclient-side (consistent withTagResource/WebhookResource/SignerResource).- Internal DRY/KISS cleanups with no behavioural change: a shared
withAccessCode/execute/toMaphelper inBaseResource, single-source base-URL normalisation, and thecertificatedliteral sourced from the existing enums.
- Fixed two non-compiling README examples (
ResendEmailResponse→ResendNotificationResponse;new CreateDocumentFromTemplateRequest(...)→ the builder form) and documented previously-undocumented methods (activities,verify,isFullySigned,getSigningProgress,waitUntilReady,downloadPage,estimateCostFromTemplate,estimateResendCost). - Added Javadoc to the public
DocumentResourcemethods; deprecated the never-populatedDocumentDetails.download_url/download_final_url(usegetArtifacts()); documented that signercpf/metadataare sent for sibling-SDK parity but not persisted/returned by the current API.
Adds the missing Tag and document-tag surfaces and API-key management, fixes
several response-parsing bugs, and tightens model conformance. 111 unit tests
pass and the opt-in LiveApiSmokeIT exercises 16 read/write flows.
This release contains source-breaking changes (corrected public types). See Changed below for migration notes.
TagResource(client.tags()):list,create,rename,delete(with aforceoverload) for workspace tags —GET/POST /accounts/{id}/tags,PUT/DELETE /accounts/{id}/tags/{tagId}. NewTagmodel andCreateTagRequest/RenameTagRequest.- Document tags on
DocumentResource:listTags,replaceTags,appendTags,detachTag—GET/PUT/POST /accounts/{id}/documents/{docId}/tagsandDELETE .../tags/{tagId}. ApiKeyResource(client.apiKeys()):get(masked),create(password)(full key, one-time),delete—/users/api-keys. NewApiKeymodel.AssignmentResourcesigner-flow endpoints:getForSigner(accessCode)(GET /sign) andsign(documentId, assignmentId, accessCode, items)(POST /documents/{id}/assignments/{id}).SignerResource.confirmSignerData(documentId, accessCode, data)— the signer self-service confirm-data endpoint, now properly URL-encoded.ListParamstyped filters:status,method,tags,includeInactive,includeStandard(previously only reachable via the untypedextra()map).SignerReference.step/TemplateSigner.stepfor sequential signing order.CreateDocumentFromTemplateRequest.tags(auto-creates tags by name).- New models:
Tag,ApiKey,SigningUrl,AssignmentSigner,AssignmentItem,ActivityOrigin,TemplatePage,TemplateFieldPlacement,ResendNotificationResponse. - Richer model fields verified against live responses:
DocumentDetails(template_id,tags,declined_by, typedpages),DocumentListItem(artifacts,signing_url,pages,tags,decline_reason,declined_by),Template/TemplateListItem(document_name,message,pages,roles,tags,default_document_tags),TemplateRole(assignment_type,created_at,updated_at),Assignment(resource). AssinafyClientOptions.SANDBOX_BASE_URLconstant.
Assignment.signingUrlsis nowList<SigningUrl>(wasMap<String,String>). The API returns an array of{signer_id, url}objects; the old type silently parsed tonulland could throw on assignment responses.Assignment.signersis nowList<AssignmentSigner>(wasList<Signer>), exposingverification_method,notification_methods,step,notified,completedandnotification_history.Assignment.itemsis nowList<AssignmentItem>. The legacyexpirationfield was removed (useexpiresAt).SignerResource.acceptTermsnow returnsSigner(wasMap<String,Object>).FieldResource.validatereturnsFieldValidationResult;validateMultiplereturnsList<FieldValidationResult>(wereMap<String,Object>). This also fixesvalidateMultiplemangling its JSON-array response.AssignmentResource.getWhatsappNotificationsreturnsList<Map<String,Object>>(wasMap<String,Object>); the API returns an array.AssignmentResource.resendNotificationreturnsResendNotificationResponse(renamed fromResendEmailResponse, since it applies to any channel).DocumentActivity.originis now anActivityOriginobject (ip,user-agent) instead ofString, matching the live payload (the oldStringtype would throw at runtime whenoriginwas populated); addedpayload.DocumentUploadResponse.declinedByis nowObject(wasString), matching the documentedobject|nullshape.WebhookPayloadnow models the documented envelope: addedorigin,subject,created_at; removed the undocumentedtypeanddatafields (getEventDatanow returns theobjectentity, falling back topayload).WebhookSubscriptiondropped the phantomidandcreated_atfields (the API returns onlyevents,is_active,url,email,updated_at).WebhookDispatch.createdAt/updatedAtare nowString(ISO-8601) per the documented Dispatch object (wereLong).
SignerResource.createno longer requires an email:emailis optional per the API, so WhatsApp-only signers can now be created.full_nameis required (matches the documented contract). The email-based dedupe pre-check is skipped when no email is supplied.AssignmentResource.resetExpiration(..., null)no longer throwsNullPointerException; passingnullcorrectly clears the expiration.confirmSignerDatanow URL-encodes thesigner-access-codequery parameter (it previously appended it raw).decline/declineMultiplenow require a non-blankdecline_reason(documented as required) instead of silently omitting it.
DocumentResource.confirmSignerData— moved toSignerResource.confirmSignerData(signer self-service). The old method remains (now correctly encoded) but is deprecated.WebhookResource.delete— theDELETE /accounts/{id}/webhooks/subscriptionsroute is not served by the live API (returns 404). Useinactivate()to stop delivery.
- The documented user-account/auth surface is intentionally limited to API-key
management (
client.apiKeys()). Login, social login, and password change/reset are web-app concerns and are out of scope for this server SDK. TemplateResource.get(id)is retained but the single-template GET path is not separately specified in the docs; verify availability before relying on it.
Fixes a pagination bug, removes a broken endpoint, and adds the missing
Field Definition, Public Document, signer-self-service, and assignment
decline / WhatsApp-notification endpoints. 82 unit tests pass and a new
LiveApiSmokeIT exercises 12 read/write flows.
FieldResource(client.fields()):create/list/get/update/deletefor field definitions.validateandvalidateMultiple(with optionalsigner-access-code).listTypes(GET /field-types).
PublicDocumentResource(client.publicDocuments()):getBasicInfo(GET /public/documents/{id}).sendToken(PUT /public/documents/{id}/send-token).
SignerResourceself-service additions:getCurrentDocument,listDocuments,downloadDocument(signer-scoped).signMultiple,declineMultiple(bulk signer actions).
AssignmentResource:decline(documentId, assignmentId, signerAccessCode, reason)— signer-side reject (PUT /documents/{id}/assignments/{id}/reject).getWhatsappNotifications(documentId, assignmentId)— delivery state.
Signermodel:has_signatureandhas_initialfields.- New models:
FieldDefinition,FieldType,FieldValidationResult. - New request types:
CreateFieldRequest,UpdateFieldRequest. OkHttpApiClient.postSignaturenow auto-detects PNG vs JPEG from the byte prefix and sets the correctContent-Type.
ListParams.toQueryParamsnow emitsper-page(hyphen) instead ofper_page(underscore). The Assinafy API silently ignored the underscore form, so all paginated calls were previously returning the default page size regardless of the configuredperPage.- Signer self-service endpoints (
getSelf,uploadSignature,downloadSignature) now URL-encode thesigner-access-codeandtypequery parameters.
AssignmentResource.cancel(...)— the previous implementation called/accounts/{id}/signature-requests/{docId}/cancel, which is not a real endpoint (returns 404). The API does not document a sender-side cancel; useDocumentResource.deleteor let the assignment expire.
- Stray indented closing braces in
AssignmentResource,WebhookResource, andWorkspaceResource. - Removed unused
ResponseHandlerimports across resources. TemplateResource.listnow passes an empty map (notnull) for query parameters when none are supplied, matching peer resources.
Initial Java port of the SDK. API parity with the TypeScript and PHP SDKs.
AssinafyClientwith builder-styleAssinafyClientOptions.- Resources:
DocumentResource,SignerResource,WorkspaceResource,AssignmentResource,WebhookResource,TemplateResource. - High-level
uploadAndRequestSignatureshelper. WebhookVerifierfor HMAC-SHA256 signature verification.- Typed exceptions:
AssinafyException,ApiException,ValidationException,NetworkException. cpffield on signer create/update payloads with automatic non-digit stripping (mirrors PHP SDKsanitizeDocument).PaginatedResult<T>with parsedX-Pagination-*header meta.