Webhooky
Webhook je adresa ve vašem programu, kterou abFaktura zavolá, když se změní doklad. Nemusíte se pak neustále ptát na změny.
Nastavení
Webhooky nastavíte v Nastavení › API klíče › Webhooky nebo přes API (oprávnění webhooks:write):
POST /api/v1/webhooks
{ "url": "https://vas-program.cz/abfaktura", "events": ["invoice.created", "invoice.paid"] }
Odpověď obsahuje secret pro ověření podpisu. Ukáže se jen jednou; nový získáte přes POST /webhooks/{id}/rotate-secret.
Firma může mít nejvýše 10 webhooků. Adresa musí být veřejná a začínat https://.
Události
| Událost | Kdy přijde |
|---|---|
invoice.created | Doklad byl vytvořen (v aplikaci, přes API, opakovanou fakturou, importem). |
invoice.updated | Doklad se změnil. |
invoice.deleted | Doklad byl smazán. |
invoice.restored | Smazaný doklad byl obnoven. |
invoice.sent | Doklad byl odeslán e-mailem. |
invoice.reminder_sent | Odešla upomínka. |
invoice.viewed | Odběratel otevřel doklad (odkaz nebo e-mail). |
invoice.payment_added | K dokladu přibyla úhrada. |
invoice.payment_deleted | Úhrada byla zrušena. |
invoice.paid | Doklad je celý zaplacený. |
ping | Zkušební událost z POST /webhooks/{id}/ping. |
Místo výčtu můžete poslat ["*"] pro všechny události.
Volání
abFaktura pošle POST s JSON tělem a hlavičkami:
POST /abfaktura HTTP/1.1
Content-Type: application/json
X-Abfaktura-Event: invoice.paid
X-Abfaktura-Delivery: 7b0c5c1e-…
X-Abfaktura-Signature: t=1792915200,v1=5f2b…
{
"id": "7b0c5c1e-…",
"type": "invoice.paid",
"createdAt": "2026-10-26T08:30:00Z",
"accountId": "…",
"data": {
"invoiceId": "…",
"documentNumber": "2026-0042",
"documentType": "issued",
"kind": "invoice",
"partner": { "name": "Odběratel s.r.o.", "companyNumber": "12345678", "taxNumber": "CZ12345678" },
"currency": "CZK",
"total": 30250,
"amountDue": 0,
"dateOfIssue": "2026-10-20T00:00:00Z",
"dueDate": "2026-11-03T00:00:00Z",
"deleted": false,
"api": "/api/v1/invoices/…"
}
}
Webhook nese jen souhrn; celý doklad si načtěte z adresy v data.api. Odpovězte do 15 sekund kódem 2xx.
Jinou odpověď zkusíme znovu po 1, 5 a 30 minutách, po 2, 6, 12 a 24 hodinách. Pak se doručení vzdáme; poslat ho znovu můžete přes
POST /webhooks/deliveries/{id}/redeliver. Stejná událost může výjimečně přijít dvakrát, poznáte ji podle id.
Ověření podpisu
Hlavička X-Abfaktura-Signature obsahuje čas odeslání t (unixové sekundy) a v1,
což je HMAC-SHA256 řetězce t + "." + tělo s vaším tajemstvím, zapsaný hexadecimálně. Porovnávejte surové tělo požadavku
a odmítněte volání starší než 5 minut.
Node.js
import crypto from "node:crypto";
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
return age < 300 && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
C#
using System.Security.Cryptography;
using System.Text;
static bool Verify(string rawBody, string header, string secret)
{
var parts = header.Split(',').Select(p => p.Split('=', 2)).ToDictionary(p => p[0], p => p[1]);
var age = Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(parts["t"]));
var mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes($"{parts["t"]}.{rawBody}"));
return age < 300 && CryptographicOperations.FixedTimeEquals(
Encoding.ASCII.GetBytes(Convert.ToHexString(mac).ToLowerInvariant()), Encoding.ASCII.GetBytes(parts["v1"]));
}
PHP
function verify(string $rawBody, string $header, string $secret): bool {
parse_str(str_replace(',', '&', $header), $parts);
$expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
return abs(time() - (int)$parts['t']) < 300 && hash_equals($expected, $parts['v1']);
}