Webhooky
KROS vie o udalostiach vo Fakturácii a Financiách informovať váš server v reálnom čase — namiesto toho, aby ste dáta museli pravidelne dopytovať. Táto stránka popisuje tri eventy, ktoré KROS posiela, ako overiť, že notifikácia naozaj prišla z KROSu, a ako čítať telo notifikácie vrátane čiastočných zlyhaní.
Tri eventy
KROS posiela tri typy notifikácií. Výber správneho — a rozlíšenie doklad vs. Financie — je dôležitý: zámena je dôvod, prečo partneri dostávajú eventy, ktoré nečakali, alebo im naopak chýbajú tie, ktoré potrebujú.
| Event | Kedy sa pošle |
|---|---|
| Document webhook | doklad vo Fakturácii bol vytvorený, zmenený alebo vymazaný. |
| Payment webhook (doklad) | platba dokladu bola vytvorená, zmenená alebo vymazaná; netýka sa zmien v module Financie. |
| Payment webhook (Financie) | platba v module Financie bola vytvorená, zmenená alebo vymazaná; nezahŕňa platby dokladov, ktoré nie sú viazané na bankový účet, platobnú bránu ani pokladnicu. |
Kde sa webhook nastavuje
Adresu svojho endpointu zadáte jedným z dvoch spôsobov: buď priamo v KROS UI v sekcii API prepojenia, do poľa URL pre príjem notifikácií (pozri Manuálny token), alebo cez parameter webhook consent URL, ak používate self-service autorizáciu (pozri Integration Consent).
Verifikácia podpisu
Kľúč aj telo sa kóduje ako UTF-16LE, nie UTF-8
Toto je najčastejšia príčina zlyhania integrácie. Podpis v hlavičke X-Kros-Signature-256 je HMAC-SHA256 nad telom požiadavky, kde sa aj telo, aj tajný kľúč kódujú ako UTF-16LE — nie ako UTF-8, ktoré je prirodzeným (a nesprávnym) predpokladom. Výsledný hash sa zapíše ako Base64. Odporúčaná dĺžka kľúča je 256 bitov. Ak kľúč alebo telo zakódujete ako UTF-8, výpočet prebehne bez chyby, ale vypočítaný podpis sa nikdy nebude zhodovať s tým, čo poslal KROS — chyba sa navonok neprejaví inak než tichým zlyhaním verifikácie.
Štyri funkčne rovnaké implementácie nižšie počítajú ten istý hash bajt po bajte. C# a PHP sú prevzaté priamo zo špecifikácie KROS Webhooks; JavaScript (Node) a Python sú napísané tak, aby dávali identický výsledok — kódovanie UTF-16LE zabezpečuje Buffer.from(s, 'utf16le') v Node a s.encode('utf-16-le') v Pythone.
private string? GenerateHash(string payload, string webHookSecret)
{
var hash = new HMACSHA256(Encoding.Unicode.GetBytes(webHookSecret));
return Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(payload)));
}
// Overenie prijatého webhooku — konštantný čas, nie ==:
var expected = GenerateHash(requestBody, webHookSecret);
var received = request.Headers["X-Kros-Signature-256"];
var isValid = CryptographicOperations.FixedTimeEquals(
Convert.FromBase64String(expected),
Convert.FromBase64String(received));
function generateHash($data, $key) {
$keyEncoded = mb_convert_encoding($key, "UTF-16LE");
$dataEncoded = mb_convert_encoding($data, "UTF-16LE");
return base64_encode(hash_hmac("sha256", $dataEncoded, $keyEncoded, true));
}
// Overenie prijatého webhooku — konštantný čas, nie ==:
$expected = generateHash($requestBody, $webHookSecret);
$received = $_SERVER['HTTP_X_KROS_SIGNATURE_256'] ?? '';
$isValid = hash_equals($expected, $received);
const crypto = require('crypto');
function generateHash(payload, webHookSecret) {
const key = Buffer.from(webHookSecret, 'utf16le');
const data = Buffer.from(payload, 'utf16le');
return crypto.createHmac('sha256', key).update(data).digest('base64');
}
// Overenie prijatého webhooku — konštantný čas, nie ==:
const expected = generateHash(requestBody, webHookSecret);
const received = req.headers['x-kros-signature-256'] || '';
const isValid =
expected.length === received.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
import base64
import hashlib
import hmac
def generate_hash(payload: str, web_hook_secret: str) -> str:
key = web_hook_secret.encode("utf-16-le")
data = payload.encode("utf-16-le")
digest = hmac.new(key, data, hashlib.sha256).digest()
return base64.b64encode(digest).decode("ascii")
# Overenie prijatého webhooku — konštantný čas, nie ==:
expected = generate_hash(request_body, web_hook_secret)
received = request.headers.get("X-Kros-Signature-256", "")
is_valid = hmac.compare_digest(expected, received)
Vo všetkých štyroch jazykoch sa porovnanie robí konštantno-časovou funkciou (CryptographicOperations.FixedTimeEquals, hash_equals, crypto.timingSafeEqual, hmac.compare_digest), nikdy operátorom == ani ===. Naivné porovnanie reťazcov sa zastaví pri prvom nezhodnom znaku, a rozdiel v čase odpovede tak útočníkovi prezradí podpis bajt po bajte, kým ho neuhádne celý.
Telo notifikácie
Príklad úspešne spracovanej dávky:
{
"companyId": 119919,
"entityType": 2,
"results": {
"entities": [{
"index": 0,
"source": 1,
"operation": 2,
"status": 201,
"data": {
"documentType": 2,
"documentId": 926523,
"variableSymbol": "4444555",
"sumOfPayment": 1
},
"problems": null
}],
"relatedEntities": []
},
"status": 200,
"requestId": "50304274-197c-40e9-8804-84e23e28efa2"
}
207 znamená čiastočné zlyhanie
Vrchný status 200 znamená, že spracovanie prebehlo úplne v poriadku. 207 znamená čiastočné zlyhanie — niektoré položky v dávke sa nespracovali a podrobnosti nájdete v poli problems príslušnej entity. Webhook handler, ktorý kontroluje iba to, že status je 200, tieto čiastočné zlyhania potichu zahodí.
Pole requestId vám umožní priradiť túto notifikáciu k pôvodnému nahratiu — je to tá istá hodnota, akú ste dostali v odpovedi 202 Accepted pri nahrávaní dokladu.
Typy problémov
| Typ | Význam |
|---|---|
resource-locked |
„Resource is locked by non-editable lock, therefore the resource cannot be edited.“ |
id-conflict |
zdroj sa nepodarilo jednoznačne identifikovať na úpravu. |
duplicate-document-number |
rovnaké číslo dokladu a rad sa v dávke vyskytuje viac ako raz. |
Tok
202 Accepted s requestId, asynchrónne spracovanie v KROSe, POST notifikácia na váš endpoint, overenie podpisu a priradenie výsledku k pôvodnému nahratiu podľa requestId.Špecifikácia
Webhooky majú vlastnú OpenAPI špecifikáciu, oddelenú od špecifikácie endpointov:
app-webhooks-swagger.json— živá špecifikácia webhookov (autoritatívna)- Lokálna kópia — tá istá špecifikácia uložená na tomto webe, s dátumom stiahnutia
- Swagger UI — otvorí sa na špecifikácii endpointov; webhooky si prepnete v rozbaľovacom zozname vpravo hore