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

  1. Log in to the Bizzlink Portal
  2. Go to Settings → API Keys
  3. Click Create Key
  4. Choose a label (e.g. “ERP Integration”) and environment (Live/Sandbox)
  5. Copy both the API Token and the HMAC Secret
Store both credentials securely. They cannot be retrieved after the initial display. If lost, create a new API key and delete the old one.

Making Requests

Every API request requires two headers:

HeaderValue
AuthorizationBearer <api-token>
X-Bizzlink-Signaturet=<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.

  1. Set these Environment Variables:

    • api_token — your Bearer token (set in the request’s Authorization tab as Bearer {{api_token}})
    • hmac_secret — your 128-character HMAC secret
  2. Add this Pre-request Script to the collection (handles pathWithQuery so 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

StatusMeaning
401Invalid or missing API key
401Missing or invalid HMAC signature
401Signature timestamp expired (> 5 min)
403API_ACCESS feature not enabled
429Rate limit exceeded