Quick Start
Send your first PEPPOL invoice in under 5 minutes.
This guide walks you through sending your first invoice via the Bizzlink API.
Prerequisites
- A Bizzlink account with API access enabled
- Your API Token and HMAC Secret from Settings → API Keys
Step 1: Verify Connectivity
The snippets below read BIZZLINK_API_TOKEN and BIZZLINK_HMAC_SECRET from environment variables so credentials never end up in code.
In production, inject them through your platform’s secret manager (AWS Secrets Manager, GCP Secret Manager, Vault, Kubernetes Secrets, 1Password CLI, …).
Signing payload format: {timestamp}.{method}.{path-and-query}.{sha256-hex(body)}
path-and-query— the full request path including the query string verbatim (/api/v1/documents?status=PENDINGfor a GET with query params, or just/api/v1/pingfor none).sha256-hex(body)— the body is hashed first, then the hex digest goes into the payload. For GET requests with no body, hash the empty string →e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.
See Authentication for the full spec.
# Fail fast if credentials are missing
: "${BIZZLINK_API_TOKEN:?BIZZLINK_API_TOKEN not set}"
: "${BIZZLINK_HMAC_SECRET:?BIZZLINK_HMAC_SECRET not set}"
TIMESTAMP=$(date +%s)
# Append ?key=value&... here if your endpoint takes query params
PATH_AND_QUERY="/bizzlink/api/v1/ping"
# GET has no body → hash the empty string
BODY_HASH=$(printf '' | openssl dgst -sha256 -hex | sed 's/^.* //')
SIGNATURE=$(printf '%s' "${TIMESTAMP}.GET.${PATH_AND_QUERY}.${BODY_HASH}" | \
openssl dgst -sha256 -hmac "${BIZZLINK_HMAC_SECRET}" | sed 's/^.* //')
curl -X GET "https://gateway.vigasoft.lu${PATH_AND_QUERY}" \
-H "Authorization: Bearer ${BIZZLINK_API_TOKEN}" \
-H "X-Bizzlink-Signature: t=${TIMESTAMP},v1=${SIGNATURE}"
// Maven: lu.vigasoft:bizzlink-sdk-java:0.3.1
// SDK takes care of HMAC signing, query-string inclusion and body hashing.
import lu.vigasoft.bizzlink.sdk.BizzlinkClient;
import lu.vigasoft.bizzlink.api.PingApi;
import lu.vigasoft.bizzlink.model.PingResponse;
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("Connected: tenant=" + pong.getTenantName());
}
import hashlib
import hmac
import os
import time
import requests
API_BASE = "https://gateway.vigasoft.lu"
# Append ?key=value&... here if your endpoint takes query params
PATH_AND_QUERY = "/bizzlink/api/v1/ping"
API_TOKEN = os.environ["BIZZLINK_API_TOKEN"]
HMAC_SECRET = os.environ["BIZZLINK_HMAC_SECRET"]
timestamp = int(time.time())
# GET has no body → hash the empty string
body_hash = hashlib.sha256(b"").hexdigest()
payload = f"{timestamp}.GET.{PATH_AND_QUERY}.{body_hash}".encode()
signature = hmac.new(
HMAC_SECRET.encode(), payload, hashlib.sha256
).hexdigest()
response = requests.get(
f"{API_BASE}{PATH_AND_QUERY}",
headers={
"Authorization": f"Bearer {API_TOKEN}",
"X-Bizzlink-Signature": f"t={timestamp},v1={signature}",
},
)
print(response.json())
// Node.js 18+ (built-in fetch + crypto)
import crypto from 'node:crypto';
const API_BASE = 'https://gateway.vigasoft.lu';
// Append ?key=value&... here if your endpoint takes query params
const PATH_AND_QUERY = '/bizzlink/api/v1/ping';
const API_TOKEN = process.env.BIZZLINK_API_TOKEN;
const HMAC_SECRET = process.env.BIZZLINK_HMAC_SECRET;
const timestamp = Math.floor(Date.now() / 1000);
// GET has no body → hash the empty string
const bodyHash = crypto.createHash('sha256').update('').digest('hex');
const payload = `${timestamp}.GET.${PATH_AND_QUERY}.${bodyHash}`;
const signature = crypto
.createHmac('sha256', HMAC_SECRET)
.update(payload)
.digest('hex');
const response = await fetch(`${API_BASE}${PATH_AND_QUERY}`, {
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'X-Bizzlink-Signature': `t=${timestamp},v1=${signature}`,
},
});
console.log(await response.json());
<?php
// composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
$apiBase = 'https://gateway.vigasoft.lu';
// Append ?key=value&... here if your endpoint takes query params
$pathAndQuery = '/bizzlink/api/v1/ping';
$apiToken = getenv('BIZZLINK_API_TOKEN');
$hmacSecret = getenv('BIZZLINK_HMAC_SECRET');
$timestamp = time();
// GET has no body → hash the empty string
$bodyHash = hash('sha256', '');
$payload = "{$timestamp}.GET.{$pathAndQuery}.{$bodyHash}";
$signature = hash_hmac('sha256', $payload, $hmacSecret);
$client = new Client();
$response = $client->get($apiBase . $pathAndQuery, [
'headers' => [
'Authorization' => "Bearer {$apiToken}",
'X-Bizzlink-Signature' => "t={$timestamp},v1={$signature}",
],
]);
echo $response->getBody();
You should get back a 200 OK with your tenant information.
Step 2: Send an Invoice
Use the Create & Send Invoice endpoint:
: "${BIZZLINK_API_TOKEN:?BIZZLINK_API_TOKEN not set}"
: "${BIZZLINK_HMAC_SECRET:?BIZZLINK_HMAC_SECRET not set}"
TIMESTAMP=$(date +%s)
PATH_AND_QUERY="/bizzlink/api/v1/document/create-and-send/invoice"
BODY='{
"invoiceNumber": "INV-2026-001",
"issueDate": "2026-02-21",
"dueDate": "2026-03-21",
"currencyCode": "EUR",
"buyerReference": "PO-12345",
"sender": {
"name": "Your Company S.A.",
"peppol": { "schemeId": "9938", "participantId": "LU12345678" },
"vatNumber": "LU12345678",
"address": {
"street": "1 Rue du Commerce", "city": "Luxembourg",
"postalCode": "1212", "country": "LU"
},
"email": "billing@yourcompany.lu",
"bankAccount": { "iban": "LU000000000000000000", "bic": "BCEELULL" }
},
"receiver": {
"name": "Acme Corp",
"peppol": { "schemeId": "9930", "participantId": "DE123456789" },
"vatNumber": "DE123456789",
"billingAddress": {
"street": "12 Hauptstrasse", "city": "Berlin",
"postalCode": "10115", "country": "DE"
}
},
"items": [
{ "name": "Consulting services", "unit": "HUR",
"quantity": 10, "price": 150.00, "vatPercent": 17 }
]
}'
BODY_HASH=$(printf '%s' "${BODY}" | openssl dgst -sha256 -hex | sed 's/^.* //')
SIGNATURE=$(printf '%s' "${TIMESTAMP}.POST.${PATH_AND_QUERY}.${BODY_HASH}" | \
openssl dgst -sha256 -hmac "${BIZZLINK_HMAC_SECRET}" | sed 's/^.* //')
curl -X POST "https://gateway.vigasoft.lu${PATH_AND_QUERY}" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${BIZZLINK_API_TOKEN}" \
-H "X-Bizzlink-Signature: t=${TIMESTAMP},v1=${SIGNATURE}" \
-d "${BODY}"
// Maven: lu.vigasoft:bizzlink-sdk-java:0.3.1
// SDK signs, hashes the body and serializes JSON automatically.
import lu.vigasoft.bizzlink.sdk.BizzlinkClient;
import lu.vigasoft.bizzlink.sdk.requests.CreateAndSendInvoiceRequest;
import lu.vigasoft.bizzlink.api.DocumentApi;
import lu.vigasoft.bizzlink.model.*;
import java.time.LocalDate;
import java.math.BigDecimal;
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")
.issueDate(LocalDate.parse("2026-02-21"))
.dueDate(LocalDate.parse("2026-03-21"))
.currencyCode(CurrencyCode.EUR)
.buyerReference("PO-12345")
.sender(new Party()
.name("Your Company S.A.")
.peppol(new PeppolId().schemeId("9938").participantId("LU12345678"))
.vatNumber("LU12345678")
.address(new Address()
.street("1 Rue du Commerce").city("Luxembourg")
.postalCode("1212").country("LU"))
.email("billing@yourcompany.lu")
.bankAccount(new BankAccount()
.iban("LU000000000000000000").bic("BCEELULL")))
.receiver(new Party()
.name("Acme Corp")
.peppol(new PeppolId().schemeId("9930").participantId("DE123456789"))
.vatNumber("DE123456789")
.billingAddress(new Address()
.street("12 Hauptstrasse").city("Berlin")
.postalCode("10115").country("DE")))
.addItemsItem(new InvoiceItem()
.name("Consulting services").unit("HUR")
.quantity(new BigDecimal("10")).price(new BigDecimal("150.00"))
.vatPercent(new BigDecimal("17")))));
System.out.println("Document ID: " + response.getData().getId());
}
import hashlib
import hmac
import json
import os
import time
import requests
API_BASE = "https://gateway.vigasoft.lu"
PATH_AND_QUERY = "/bizzlink/api/v1/document/create-and-send/invoice"
API_TOKEN = os.environ["BIZZLINK_API_TOKEN"]
HMAC_SECRET = os.environ["BIZZLINK_HMAC_SECRET"]
invoice = {
"invoiceNumber": "INV-2026-001",
"issueDate": "2026-02-21",
"dueDate": "2026-03-21",
"currencyCode": "EUR",
"buyerReference": "PO-12345",
"sender": {
"name": "Your Company S.A.",
"peppol": {"schemeId": "9938", "participantId": "LU12345678"},
"vatNumber": "LU12345678",
"address": {
"street": "1 Rue du Commerce", "city": "Luxembourg",
"postalCode": "1212", "country": "LU",
},
"email": "billing@yourcompany.lu",
"bankAccount": {"iban": "LU000000000000000000", "bic": "BCEELULL"},
},
"receiver": {
"name": "Acme Corp",
"peppol": {"schemeId": "9930", "participantId": "DE123456789"},
"vatNumber": "DE123456789",
"billingAddress": {
"street": "12 Hauptstrasse", "city": "Berlin",
"postalCode": "10115", "country": "DE",
},
},
"items": [
{"name": "Consulting services", "unit": "HUR",
"quantity": 10, "price": 150.00, "vatPercent": 17},
],
}
# IMPORTANT: serialize once, then both hash and send the exact same bytes
body = json.dumps(invoice, separators=(",", ":")).encode()
timestamp = int(time.time())
body_hash = hashlib.sha256(body).hexdigest()
payload = f"{timestamp}.POST.{PATH_AND_QUERY}.{body_hash}".encode()
signature = hmac.new(HMAC_SECRET.encode(), payload, hashlib.sha256).hexdigest()
response = requests.post(
f"{API_BASE}{PATH_AND_QUERY}",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_TOKEN}",
"X-Bizzlink-Signature": f"t={timestamp},v1={signature}",
},
data=body,
)
print(response.json())
// Node.js 18+ (built-in fetch + crypto)
import crypto from 'node:crypto';
const API_BASE = 'https://gateway.vigasoft.lu';
const PATH_AND_QUERY = '/bizzlink/api/v1/document/create-and-send/invoice';
const API_TOKEN = process.env.BIZZLINK_API_TOKEN;
const HMAC_SECRET = process.env.BIZZLINK_HMAC_SECRET;
const invoice = {
invoiceNumber: 'INV-2026-001',
issueDate: '2026-02-21',
dueDate: '2026-03-21',
currencyCode: 'EUR',
buyerReference: 'PO-12345',
sender: {
name: 'Your Company S.A.',
peppol: { schemeId: '9938', participantId: 'LU12345678' },
vatNumber: 'LU12345678',
address: {
street: '1 Rue du Commerce', city: 'Luxembourg',
postalCode: '1212', country: 'LU',
},
email: 'billing@yourcompany.lu',
bankAccount: { iban: 'LU000000000000000000', bic: 'BCEELULL' },
},
receiver: {
name: 'Acme Corp',
peppol: { schemeId: '9930', participantId: 'DE123456789' },
vatNumber: 'DE123456789',
billingAddress: {
street: '12 Hauptstrasse', city: 'Berlin',
postalCode: '10115', country: 'DE',
},
},
items: [
{ name: 'Consulting services', unit: 'HUR',
quantity: 10, price: 150.00, vatPercent: 17 },
],
};
// IMPORTANT: serialize once, then both hash and send the exact same bytes
const body = JSON.stringify(invoice);
const timestamp = Math.floor(Date.now() / 1000);
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const payload = `${timestamp}.POST.${PATH_AND_QUERY}.${bodyHash}`;
const signature = crypto.createHmac('sha256', HMAC_SECRET)
.update(payload).digest('hex');
const response = await fetch(`${API_BASE}${PATH_AND_QUERY}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_TOKEN}`,
'X-Bizzlink-Signature': `t=${timestamp},v1=${signature}`,
},
body,
});
console.log(await response.json());
<?php
// composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
$apiBase = 'https://gateway.vigasoft.lu';
$pathAndQuery = '/bizzlink/api/v1/document/create-and-send/invoice';
$apiToken = getenv('BIZZLINK_API_TOKEN');
$hmacSecret = getenv('BIZZLINK_HMAC_SECRET');
$invoice = [
'invoiceNumber' => 'INV-2026-001',
'issueDate' => '2026-02-21',
'dueDate' => '2026-03-21',
'currencyCode' => 'EUR',
'buyerReference'=> 'PO-12345',
'sender' => [
'name' => 'Your Company S.A.',
'peppol' => ['schemeId' => '9938', 'participantId' => 'LU12345678'],
'vatNumber' => 'LU12345678',
'address' => [
'street' => '1 Rue du Commerce', 'city' => 'Luxembourg',
'postalCode' => '1212', 'country' => 'LU',
],
'email' => 'billing@yourcompany.lu',
'bankAccount' => ['iban' => 'LU000000000000000000', 'bic' => 'BCEELULL'],
],
'receiver' => [
'name' => 'Acme Corp',
'peppol' => ['schemeId' => '9930', 'participantId' => 'DE123456789'],
'vatNumber' => 'DE123456789',
'billingAddress' => [
'street' => '12 Hauptstrasse', 'city' => 'Berlin',
'postalCode' => '10115', 'country' => 'DE',
],
],
'items' => [
['name' => 'Consulting services', 'unit' => 'HUR',
'quantity' => 10, 'price' => 150.00, 'vatPercent' => 17],
],
];
// IMPORTANT: serialize once, then both hash and send the exact same bytes
$body = json_encode($invoice, JSON_UNESCAPED_SLASHES);
$timestamp = time();
$bodyHash = hash('sha256', $body);
$payload = "{$timestamp}.POST.{$pathAndQuery}.{$bodyHash}";
$signature = hash_hmac('sha256', $payload, $hmacSecret);
$client = new Client();
$response = $client->post($apiBase . $pathAndQuery, [
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => "Bearer {$apiToken}",
'X-Bizzlink-Signature' => "t={$timestamp},v1={$signature}",
],
'body' => $body,
]);
echo $response->getBody();
Step 3: Check the Response
A successful response is a JSON:API collection — data is an array of generated renditions.
For a typed-invoice call from a LU/AT/BE/NL/… seller this is a single-element array (UBL via
Peppol). For an IT seller, the same input would produce two entries (FatturaPA via SDI and
UBL via Peppol), sharing a processId.
{
"data": [
{
"type": "invoices",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"attributes": {
"processId": "8e3a1f2c-1234-4abc-9def-012345678abc",
"format": "UBL_PEPPOL_BIS_3",
"target": "PEPPOL",
"status": "QUEUED_FOR_VALIDATION",
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Invoice>...</Invoice>"
}
}
]
}
| Field | Description |
|---|---|
data[].type | JSON:API resource type — invoices or credit-notes (per rendition). |
data[].id | Unique document id for this rendition. Use it to poll GET /documents/{id}/status. |
attributes.processId | Shared across all renditions from one create call. Use GET /documents?processId={uuid} to list siblings. |
attributes.format | Wire format of the generated XML (UBL_PEPPOL_BIS_3, FATTURA_PA_1_2, FACTUR_X_1_07, XRECHNUNG_3_0, KSEF_FA_3). |
attributes.target | Destination network (PEPPOL, SDI_IT, PPF_FR, XRECHNUNG_DE, KSEF_PL). |
attributes.status | Symbolic status — typically QUEUED_FOR_VALIDATION (async validation pending), VALID, or FAILED. |
attributes.xml | The generated XML in the indicated format. |
What’s Next?
- Send a Credit Note — Issue refunds or corrections
- Convert ZUGFeRD documents — If you already have CII/ZUGFeRD invoices
- Validate without sending — Test your XML before submission
- Schema Reference — Full field-by-field documentation