Authentication
How to authenticate your requests to the Bizzlink API.
All API requests require two credentials:
- API Token — identifies your tenant (Bearer header)
- HMAC Secret — signs every request (X-Bizzlink-Signature header)
Both are generated when you create an API key and shown only once.
Getting Your Credentials
- Log in to the Bizzlink Portal
- Go to Settings → API Keys
- Click Create Key
- Choose a label (e.g. “ERP Integration”) and environment (Live/Sandbox)
- Copy both the API Token and the HMAC Secret
Making Requests
Every API request requires two headers:
| Header | Value |
|---|---|
Authorization | Bearer <api-token> |
X-Bizzlink-Signature | t=<timestamp>,v1=<hmac-sha256-hex> |
Signature Format
X-Bizzlink-Signature: t=1713100800,v1=a1b2c3d4e5f6...
t— Unix timestamp in seconds (current time)v1— HMAC-SHA256 hex digest of the signing payload
Signing Payload
The payload to sign is:
{timestamp}.{method}.{path-and-query}.{sha256-hex(body)}
path-and-query— the request path including the query string verbatim if present. Examples:/bizzlink/api/v1/document/abc123/status(no query) or/bizzlink/api/v1/documents?status=PENDING&limit=50(with query). The?and everything after it MUST be part of the signed string; the gateway re-derives it the same way and any difference fails the signature check.sha256-hex(body)— the body is SHA-256 hashed (lowercase hex) before being included. Keeps the signed string compact for large payloads while still cryptographically protecting body integrity.
POST with body:
1713100800.POST./bizzlink/api/v1/document/send/ubl21.4f5a8e2b1c3d...
(where 4f5a8e2b1c3d... is sha256-hex({"ublXml":"<Invoice>...</Invoice>"}))
GET without body — use the SHA-256 hash of the empty string:
1713100800.GET./bizzlink/api/v1/document/abc123/status.e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
GET with query string — query goes into the signed payload:
1713100800.GET./bizzlink/api/v1/documents?status=PENDING&limit=50.e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Timestamp Tolerance
Signatures are valid for 5 minutes (300 seconds). Requests with an older timestamp are rejected.
Examples
The snippets below show the raw signing protocol. If a language has an official SDK, use it — the SDK handles signing for you. All examples read BIZZLINK_API_TOKEN and BIZZLINK_HMAC_SECRET from the environment (see Quick Start for how to set them).
: "${BIZZLINK_API_TOKEN:?BIZZLINK_API_TOKEN not set}"
: "${BIZZLINK_HMAC_SECRET:?BIZZLINK_HMAC_SECRET not set}"
TIMESTAMP=$(date +%s)
METHOD="POST"
# Append ?key=value&... here if your endpoint takes query params
PATH_AND_QUERY="/bizzlink/api/v1/document/send/ubl21"
BODY='{"ublXml":"<Invoice>...</Invoice>"}'
BODY_HASH=$(printf '%s' "${BODY}" | openssl dgst -sha256 -hex | sed 's/^.* //')
SIGNATURE=$(printf '%s' "${TIMESTAMP}.${METHOD}.${PATH_AND_QUERY}.${BODY_HASH}" | \
openssl dgst -sha256 -hmac "${BIZZLINK_HMAC_SECRET}" | sed 's/^.* //')
curl -X ${METHOD} "https://gateway.vigasoft.lu${PATH_AND_QUERY}" \
-H "Authorization: Bearer ${BIZZLINK_API_TOKEN}" \
-H "X-Bizzlink-Signature: t=${TIMESTAMP},v1=${SIGNATURE}" \
-H "Content-Type: application/json" \
-d "${BODY}"
// Raw signing — for production use the SDK instead:
// Maven: lu.vigasoft:bizzlink-sdk-java:0.3.1
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
String apiToken = System.getenv("BIZZLINK_API_TOKEN");
String hmacSecret = System.getenv("BIZZLINK_HMAC_SECRET");
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String method = "POST";
// Append ?key=value&... here if your endpoint takes query params
String pathAndQuery = "/bizzlink/api/v1/document/send/ubl21";
// Java 15+ text block — no backslash-escape hell
String body = """
{"ublXml":"<Invoice>...</Invoice>"}""";
String bodyHash = HexFormat.of().formatHex(
MessageDigest.getInstance("SHA-256").digest(body.getBytes(StandardCharsets.UTF_8)));
String payload = timestamp + "." + method + "." + pathAndQuery + "." + bodyHash;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(hmacSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String signature = HexFormat.of().formatHex(
mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)));
// Set headers on your HTTP request:
// Authorization: Bearer <apiToken>
// X-Bizzlink-Signature: t=<timestamp>,v1=<signature>
// Content-Type: application/json
// Body must be the EXACT same bytes used to compute bodyHash.
import hashlib
import hmac
import json
import os
import time
import requests
API_TOKEN = os.environ["BIZZLINK_API_TOKEN"]
HMAC_SECRET = os.environ["BIZZLINK_HMAC_SECRET"]
timestamp = str(int(time.time()))
method = "POST"
# Append ?key=value&... here if your endpoint takes query params
path_and_query = "/bizzlink/api/v1/document/send/ubl21"
# Serialize once — same bytes are hashed and sent
body = json.dumps({"ublXml": "<Invoice>...</Invoice>"}, separators=(",", ":")).encode()
body_hash = hashlib.sha256(body).hexdigest()
payload = f"{timestamp}.{method}.{path_and_query}.{body_hash}".encode()
signature = hmac.new(HMAC_SECRET.encode(), payload, hashlib.sha256).hexdigest()
response = requests.post(
f"https://gateway.vigasoft.lu{path_and_query}",
headers={
"Authorization": f"Bearer {API_TOKEN}",
"X-Bizzlink-Signature": f"t={timestamp},v1={signature}",
"Content-Type": "application/json",
},
data=body,
)
// Node.js 18+ (built-in fetch + crypto)
import crypto from 'node:crypto';
const API_TOKEN = process.env.BIZZLINK_API_TOKEN;
const HMAC_SECRET = process.env.BIZZLINK_HMAC_SECRET;
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = 'POST';
// Append ?key=value&... here if your endpoint takes query params
const pathAndQuery = '/bizzlink/api/v1/document/send/ubl21';
// Serialize once — same bytes are hashed and sent
const body = JSON.stringify({ ublXml: '<Invoice>...</Invoice>' });
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const payload = `${timestamp}.${method}.${pathAndQuery}.${bodyHash}`;
const signature = crypto.createHmac('sha256', HMAC_SECRET)
.update(payload).digest('hex');
const response = await fetch(`https://gateway.vigasoft.lu${pathAndQuery}`, {
method,
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'X-Bizzlink-Signature': `t=${timestamp},v1=${signature}`,
'Content-Type': 'application/json',
},
body,
});
<?php
// composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
$apiToken = getenv('BIZZLINK_API_TOKEN');
$hmacSecret = getenv('BIZZLINK_HMAC_SECRET');
$timestamp = time();
$method = 'POST';
// Append ?key=value&... here if your endpoint takes query params
$pathAndQuery = '/bizzlink/api/v1/document/send/ubl21';
// Serialize once — same bytes are hashed and sent
$body = json_encode(['ublXml' => '<Invoice>...</Invoice>'], JSON_UNESCAPED_SLASHES);
$bodyHash = hash('sha256', $body);
$payload = "{$timestamp}.{$method}.{$pathAndQuery}.{$bodyHash}";
$signature = hash_hmac('sha256', $payload, $hmacSecret);
$client = new Client();
$response = $client->post('https://gateway.vigasoft.lu' . $pathAndQuery, [
'headers' => [
'Authorization' => "Bearer {$apiToken}",
'X-Bizzlink-Signature' => "t={$timestamp},v1={$signature}",
'Content-Type' => 'application/json',
],
'body' => $body,
]);
Postman
Use a Pre-request Script to compute the signature automatically.
Set these Environment Variables:
api_token— your Bearer token (set in the request’s Authorization tab asBearer {{api_token}})hmac_secret— your 128-character HMAC secret
Add this Pre-request Script to the collection (handles
pathWithQueryso GET requests with query params sign correctly):
const hmacSecret = pm.environment.get("hmac_secret");
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = pm.request.method;
const fullUrl = pm.variables.replaceIn(pm.request.url.toString());
const pathWithQuery = fullUrl.replace(/^https?:\/\/[^\/]+/, "");
const body = pm.request.body ? pm.request.body.raw || "" : "";
const enc = new TextEncoder();
// Body-hash
const bodyHashBuf = await crypto.subtle.digest("SHA-256", enc.encode(body));
const bodyHash = Array.from(new Uint8Array(bodyHashBuf))
.map(b => b.toString(16).padStart(2, "0")).join("");
const payload = timestamp + "." + method + "." + pathWithQuery + "." + bodyHash;
const key = await crypto.subtle.importKey("raw", enc.encode(hmacSecret),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
const sigBuffer = await crypto.subtle.sign("HMAC", key, enc.encode(payload));
const signature = Array.from(new Uint8Array(sigBuffer))
.map(b => b.toString(16).padStart(2, "0")).join("");
pm.request.headers.upsert({
key: "X-Bizzlink-Signature",
value: "t=" + timestamp + ",v1=" + signature
});
Every request in the collection will automatically include the correct X-Bizzlink-Signature header. Set the Authorization: Bearer {{api_token}} header on the collection-level Authorization tab so it inherits to every request.
Key Management
- Create multiple keys per tenant (e.g. one per integration)
- Revoke keys instantly — revoked keys are rejected immediately
- Delete keys permanently
- Monitor usage via the “Last Used” column
To rotate credentials: create a new key, migrate your integration, then delete the old key.
Partners can manage API keys for their customers via Customers → [Customer] → API Keys.
Feature Access
API access requires the API_ACCESS feature to be enabled on your plan. If you receive a 403 Forbidden response, contact your administrator or partner to verify your plan includes API access.
Error Responses
| Status | Meaning |
|---|---|
401 | Invalid or missing API key |
401 | Missing or invalid HMAC signature |
401 | Signature timestamp expired (> 5 min) |
403 | API_ACCESS feature not enabled |
429 | Rate limit exceeded |