Official SDKs
Use a maintained client library instead of crafting HTTP requests by hand.
If you prefer not to deal with HMAC signing, request building and JSON parsing by hand, use one of the official SDKs. The SDK signs every request automatically and exposes typed methods for every endpoint.
Java
The Bizzlink Java SDK is published on Maven Central.
Add the dependency
Maven (pom.xml):
<dependency>
<groupId>lu.vigasoft</groupId>
<artifactId>bizzlink-sdk-java</artifactId>
<version>0.3.1</version>
</dependency>
Gradle (build.gradle.kts):
implementation("lu.vigasoft:bizzlink-sdk-java:0.3.1")
Requirements: Java 25 or newer.
Minimal example
import lu.vigasoft.bizzlink.sdk.BizzlinkClient;
import lu.vigasoft.bizzlink.sdk.generated.api.PingApi;
import lu.vigasoft.bizzlink.sdk.generated.model.PingResponse;
public class Example {
public static void main(String[] args) throws Exception {
try (BizzlinkClient client = BizzlinkClient.builder()
.apiToken(System.getenv("BIZZLINK_API_TOKEN"))
.hmacSecret(System.getenv("BIZZLINK_HMAC_SECRET"))
.build()) {
PingResponse pong = client.api(PingApi::new).ping();
System.out.println("Status: " + pong.getStatus());
}
}
}
That’s the whole authentication flow — the SDK reads your API token and HMAC secret once, then signs every outgoing request transparently. No need to compute timestamps, build signature payloads or set headers manually.
Sending a UBL XML document
If you already have a UBL 2.1 Invoice or Credit Note XML on disk, the SDK has a fluent helper that reads the file, signs the request and sends it through Peppol:
import lu.vigasoft.bizzlink.sdk.BizzlinkClient;
import lu.vigasoft.bizzlink.sdk.generated.api.DocumentApi;
import lu.vigasoft.bizzlink.sdk.requests.SendUblDocumentRequest;
import java.nio.file.Path;
try (BizzlinkClient client = BizzlinkClient.builder()
.apiToken(System.getenv("BIZZLINK_API_TOKEN"))
.hmacSecret(System.getenv("BIZZLINK_HMAC_SECRET"))
.build()) {
var response = client.api(DocumentApi::new)
.sendUblDocument(new SendUblDocumentRequest()
.ublXml(Path.of("invoice.xml")));
System.out.println("Document ID: " + response.getDocumentId());
}
The SendUblDocumentRequest class is a thin SDK convenience wrapper over the generated request envelope — it pre-builds the JSON:API data.type slot and exposes overloads for Path, InputStream and byte[], so you don’t have to read the file or wrap the envelope manually.
Sending a JSON invoice
If you don’t have a UBL XML yet and want the API to build it for you from a simplified JSON model:
import lu.vigasoft.bizzlink.sdk.BizzlinkClient;
import lu.vigasoft.bizzlink.sdk.generated.api.DocumentApi;
import lu.vigasoft.bizzlink.sdk.requests.CreateAndSendInvoiceRequest;
try (BizzlinkClient client = BizzlinkClient.builder()
.apiToken(System.getenv("BIZZLINK_API_TOKEN"))
.hmacSecret(System.getenv("BIZZLINK_HMAC_SECRET"))
.build()) {
var response = client.api(DocumentApi::new)
.createAndSendInvoice(new CreateAndSendInvoiceRequest()
.attributes(inv -> inv
.invoiceNumber("INV-2026-001")
// ... see Schema Reference for the full shape
));
System.out.println("Document ID: " + response.getDocumentId());
}
See the Schema Reference for the complete InvoiceJsonRequest shape.
Convenience wrappers for every mutation endpoint
The lu.vigasoft.bizzlink.sdk.requests package contains hand-written subclasses of every generated *Request envelope so you don’t have to construct the JSON:API data.type + data.attributes boilerplate yourself. Available wrappers:
| Wrapper | Wraps | Use with |
|---|---|---|
SendUblDocumentRequest | UBL XML send (with Path / InputStream / byte[] overloads) | DocumentApi.sendUblDocument(...) |
CreateAndSendInvoiceRequest | JSON invoice → UBL → Peppol | DocumentApi.createAndSendInvoice(...) |
CreateAndSendCreditNoteRequest | JSON credit note → UBL → Peppol | DocumentApi.createAndSendCreditNote(...) |
CreateAndSendUblJsonV2Request | OASIS UBL-JSON v2 → Peppol | DocumentApi.createAndSendUblJsonV2(...) |
ValidateXmlRequest | UBL XML validation only | ValidationApi.validateXml(...) |
CreateWebhookRequest / UpdateWebhookRequest | Webhook config CRUD | WebhooksApi.createWebhook(...) / updateWebhook(...) |
CreatePartnerTenantRequest / UpdatePartnerTenantRequest | Sub-tenant CRUD (Partner API) | PartnerTenantsApi.* |
CreateLegalEntityRequest / UpdateLegalEntityRequest | Legal entity CRUD (Partner API) | PartnerLegalEntitiesApi.* |
CreatePeppolIdRequest | Peppol ID assignment (Partner API) | PartnerPeppolIdsApi.createPeppolId(...) |
CreateNotificationEmailRequest / UpdateNotificationEmailRequest | Inbound notification config | NotificationEmailsApi.* |
CreateEmailTemplateRequest / UpdateEmailTemplateRequest | Email template CRUD | EmailTemplatesApi.* |
Each wrapper exposes:
attributes()— direct access to the inner DTOattributes(Consumer<T>)— fluent lambda configurationscope(JsonApiScope)— set the JSON:API ownership scope (useScopes.tenant(),Scopes.legalEntity(peppolId),Scopes.partner())
Example with scope:
client.api(WebhooksApi::new).createWebhook(
new CreateWebhookRequest()
.scope(Scopes.legalEntity("9930:LU12345678"))
.attributes(w -> w
.url("https://my.app/webhook")
.events(List.of("document.received"))));
Sandbox vs Production
The environment is encoded in the API token itself — there is one gateway URL (https://gateway.vigasoft.lu/bizzlink) and the gateway routes your request based on whether you authenticated with a sandbox or production key. No client-side configuration needed: paste a sandbox key for sandbox calls, paste a production key for production calls.
Storing your credentials
Your API token and HMAC secret are equivalent to a username and password — anyone holding them can issue invoices in your name. Treat them accordingly.
Never do this
- Hardcode in source code. Even in a private repo, the credentials end up in git history forever and leak via stack traces, log lines, screen-shares and AI tools.
- Commit
.envor config files containing secrets. Add them to.gitignorefrom day one. - Embed in client-side code. Mobile apps, browser JavaScript and desktop binaries can be decompiled — anything shipped to a user device is public.
- Share via email, chat or ticket systems. These channels are logged, archived and frequently breached.
Recommended: secret manager
For production workloads, store credentials in a dedicated secret manager and inject them at runtime as environment variables or process arguments:
| Platform | Service |
|---|---|
| AWS | Secrets Manager, Parameter Store |
| Azure | Key Vault |
| GCP | Secret Manager |
| Self-hosted | HashiCorp Vault, Bitwarden Secrets Manager, Infisical |
| Kubernetes | Sealed Secrets, External Secrets Operator |
The Java SDK examples above read from System.getenv("BIZZLINK_API_TOKEN") — the same pattern works whether the env var comes from a secret manager, a systemd unit file, a Docker --env-file, or a Kubernetes Secret.
Acceptable for local development
For local dev only, a .env file (gitignored) loaded via tooling such as direnv, dotenv-java or your IDE’s run-configuration is fine. Keep one separate set of credentials per developer — don’t share a single dev key across the team.
Spring Boot integration
If you use Spring Boot, externalize via application.yml:
bizzlink:
api-token: ${BIZZLINK_API_TOKEN}
hmac-secret: ${BIZZLINK_HMAC_SECRET}
@Configuration
public class BizzlinkConfig {
@Bean(destroyMethod = "close")
BizzlinkClient bizzlinkClient(
@Value("${bizzlink.api-token}") String apiToken,
@Value("${bizzlink.hmac-secret}") String hmacSecret) {
return BizzlinkClient.builder()
.apiToken(apiToken)
.hmacSecret(hmacSecret)
.build();
}
}
Spring resolves ${BIZZLINK_API_TOKEN} from the environment, so the actual secret never lives in your repo.
Rotation
Rotate credentials at least once a year, immediately after any team member with access leaves, and immediately if a leak is suspected. Rotate via Settings → API Keys — generate a new key, deploy it, then revoke the old one.
Other languages
SDKs for other languages are on the roadmap. In the meantime, build directly against the OpenAPI specification — every official SDK is generated from this same spec.