Webhooks API
Referenční popis webhooků: hlavičky, obálka a data, která aplikace skutečně posílá.
Jak webhook založit a ověřit jeho podpis, najdete na stránce Webhooks.
HTTP hlavičky
Každý webhook je požadavek POST s těmito hlavičkami:
| Hlavička | Popis | Příklad |
|---|---|---|
Content-Type | Formát těla | application/json |
X-MujVykaz-Event | Název události | time_entry.approved |
X-MujVykaz-Signature | HMAC-SHA256 z celého těla požadavku, klíčem je tajný klíč webhooku | sha256=a1b2c3... |
X-MujVykaz-Delivery | Unikátní ID doručení (UUID) — podle něj poznáte opakovaný pokus | 550e8400-e29b-41d4-a716-446655440000 |
User-Agent | Odesílatel | MujVykaz-Webhook/1.0 |
Obálka
Tělo má vždy tři pole — event, timestamp a data:
{
"event": "time_entry.approved",
"timestamp": "2026-03-27T13:00:04.512Z",
"data": { "id": "3f2b9c1e-8a4d-4c1b-9f0e-2d7a6b5c4e3f" }
}
timestampje čas odeslání v UTC.- Jeden požadavek nese jeden záznam. Hromadná akce (například schválení 20 výkazů) pošle 20 požadavků.
- Všechna ID jsou UUID, názvy polí jsou camelCase, časy v UTC (ISO 8601) a částky v nejmenší jednotce měny — CZK v celých korunách, EUR a USD v centech.
Dostupné události
Aplikace zná 24 událostí a všechny jdou zaškrtnout v dialogu Nový webhook.
| Skupina | Události |
|---|---|
| Výkazy | time_entry.created, time_entry.updated, time_entry.deleted, time_entry.submitted, time_entry.approved, time_entry.invoiced |
| Faktury | invoice.created, invoice.sent, invoice.paid, invoice.cancelled |
| Náklady | expense.invoiced — jen uzavření nákladu. Vytvoření, úpravu, smazání ani schválení nákladu webhook neposílá. |
| Úkoly | task.created, task.updated, task.completed, task.deleted |
| Tým | user.created, user.invited |
| Projekty | project.created, project.updated |
| Klienti | client.created, client.updated |
| Žádosti o přístup ke klientovi | access_request.created, access_request.approved, access_request.rejected |
Komentáře webhook nemají záměrně — jejich text je citlivá komunikace a posílat ho do cizího systému nechceme.
Dialog Nový webhook (Nastavení → Webhooky) nabízí všech 24 událostí ve skupinách Výkazy (6), Faktury (4), Náklady (1), Úkoly (4), Tým (2), Projekty (2), Klienti (2) a Žádosti o přístup (3). Skupina Faktury se skryje, když má organizace vypnutou fakturaci — pak zbývá 20 událostí.
Do 8. 10. 2026 dialog nabízel jen 15 událostí; události úkolů, žádostí o přístup a uzavření výkazu a nákladu nešly zaškrtnout.
Data podle události
Výkazy: created, updated, submitted, approved
data je výkaz:
{
"event": "time_entry.approved",
"timestamp": "2026-03-27T13:00:04.512Z",
"data": {
"id": "3f2b9c1e-8a4d-4c1b-9f0e-2d7a6b5c4e3f",
"userId": "7c1d2e3f-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
"projectId": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
"description": "Implementace API",
"startedAt": "2026-03-27T08:00:00.000Z",
"endedAt": null,
"durationMinutes": 150,
"hourlyRate": 1500,
"amount": 3750,
"currency": "CZK",
"status": "approved",
"isBillable": true,
"activityId": null,
"taskId": null,
"createdAt": "2026-03-27T10:31:12.000Z",
"updatedAt": "2026-03-27T13:00:04.000Z"
}
}
startedAtje v UTC:08:00Zje 9:00 pražského času v zimě, 10:00 v létě.- Organizace bez schvalování pošle při odeslání výkazu rovnou
time_entry.approved, netime_entry.submitted. - Pole
statusukazuje stav po akci, kterou událost hlásí:time_entry.submittednesesubmitted,time_entry.approvedneseapproved. - Kdo výkaz schválil a kdy, webhook nenese.
Výkazy: deleted a invoiced
time_entry.deleted nese jen ID smazaného výkazu:
{
"event": "time_entry.deleted",
"timestamp": "2026-03-27T15:12:40.201Z",
"data": { "id": "3f2b9c1e-8a4d-4c1b-9f0e-2d7a6b5c4e3f" }
}
time_entry.invoiced (výkaz uzavřený fakturou nebo uzávěrkou) nese výkaz se stejnými 16 poli jako ostatní události výkazů výše a status je už invoiced. Chodí při přidání výkazu do konceptu faktury a při uzavření v Uzávěrkách (i externí fakturou nebo obdobím paušálu). Vystavení nové faktury s výkazy (Nová faktura, faktura z klientského reportu) ji neposílá — chodí jen invoice.created.
Fakturu ani klienta data výkazu nenesou. Výkaz si podle id přečtete přes GET /api/v1/time-entries/{id}.
Data výkazu jsou u všech událostí time_entry.* jen pole z příkladu výše. Náklad člověka (odvozený ze mzdy), odkaz na událost v osobním kalendáři ani interní metadata výkazu na webhook neodcházejí.
Faktury
invoice.created, invoice.sent (i když fakturu odešlete e-mailem přímo z aplikace), invoice.paid a invoice.cancelled nesou záznam faktury — mimo jiné id, number, clientId, totalAmount (základ daně), vatRate, vatAmount, totalWithVat, currency, status, issuedAt, dueAt, paidAt a cancelledAt.
Výjimka: invoice.created u faktury vystavené z klientského reportu nese jen souhrn:
{
"event": "invoice.created",
"timestamp": "2026-03-31T09:15:00.000Z",
"data": {
"invoiceId": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
"number": "FV-2026-042",
"clientId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"totalAmount": 46350,
"currency": "CZK",
"createdFromReportId": "8e7d6c5b-4a3f-4e2d-1c0b-9a8f7e6d5c4b"
}
}
Stejně krátký souhrn — jen invoiceId, number a status — nesou invoice.sent a invoice.paid při hromadné změně stavu (v přehledu faktur i přes PUT /api/v1/invoices/status).
ID faktury proto čtěte jako data.id ?? data.invoiceId. Organizace s vypnutou fakturací události invoice.* neposílá.
Faktura vystavená automaticky ze šablony opakované faktury a hromadné vystavení faktur invoice.created zatím neposílají.
Projekty a klienti
Při založení nebo úpravě v aplikaci nese project.created / project.updated celý záznam projektu a client.created / client.updated celý záznam klienta (id, name a další pole).
Import z CSV a hromadné akce posílají většinou jen souhrn: projectId nebo clientId a name; hromadná změna stavu projektu přidá status, hromadná archivace klienta isActive. ID proto čtěte jako data.id ?? data.projectId, resp. data.id ?? data.clientId.
Úkoly
task.created, task.updated a task.completed nesou úkol (id, projectId, title, status, assigneeId, dueDate a další pole). task.deleted nese jen id, title a projectId.
Tým
user.created nese userId, name, email a role (některé cesty, třeba hromadné zakládání, jen userId a name). user.invited nese email a role pozvaného.
Náklady
expense.invoiced nese náklad uzavřený fakturou nebo uzávěrkou, vždy stejných 12 polí se stavem po uzavření: id, projectId, invoiceId (u uzavření bez faktury v aplikaci null), status (invoiced), expenseDate, description, categoryId, billableAmount (částka pro klienta), currency, isBillable, createdAt a updatedAt. Nákupní cenu, interní poznámku ani odkaz na účtenku webhook nenese.
Chodí při přidání nákladu do konceptu faktury a při uzavření v Uzávěrkách. Vystavení nové faktury s náklady ji neposílá — chodí jen invoice.created.
Žádosti o přístup ke klientovi
access_request.created nese requestId, clientId, requesterId a reason; access_request.approved nese requestId, clientId a requesterId; access_request.rejected navíc rejectReason.
HMAC ověření
Podpis se počítá z celého těla požadavku (raw body) pomocí tajného klíče webhooku:
HMAC-SHA256(secret, raw_body) → hex digest → "sha256=" + digest
Vždy používejte timing-safe porovnání (viz Webhooks nastavení).
Retry politika
| Pokus | Čekání |
|---|---|
| 1. (původní) | Okamžitě |
| 2. pokus | ~2 sekundy |
| 3. pokus | ~10 sekund |
Opakuje se jen to, co se může samo spravit: chyba serveru (5xx), výpadek spojení nebo žádná odpověď do 10 sekund. Odpověď 3xx nebo 4xx se neopakuje — přesměrování nenásledujeme a chyba na straně klienta se opakováním nespraví. Po třetím neúspěšném pokusu se doručení vzdáme.
Auto-disable
Pokud váš endpoint opakovaně selhává, webhook se po 10 po sobě jdoucích neúspěšných doručeních automaticky deaktivuje. Neúspěšné doručení je i odpověď 3xx nebo 4xx. Jediné úspěšné doručení počítadlo vynuluje.
Aplikace vás na to sama neupozorní — nepřijde e-mail ani zpráva v aplikaci. Počet chyb i to, jestli je webhook aktivní, uvidíte v Nastavení → Webhooky: deaktivovaný webhook má šedou tečku, červenou větu „Vypnuto automaticky po 10 chybách doručení v řadě — opravte adresu a zapněte ho znovu" a počet chyb (u odpovědi 3xx nebo 4xx i její HTTP kód). Postup: opravte endpoint, ověřte ho tlačítkem Test (musí vrátit 2xx) a klikněte na Aktivovat. Aktivace počítadlo chyb vynuluje, takže má webhook znovu 10 pokusů. Tlačítko Test počítadlo nemění. Když je pro vás doručování kritické, hlídejte si stav na své straně.
Aplikace čeká na odpověď nejvýš 10 sekund. Odpovězte hned (200) a webhook zpracujte asynchronně — jinak se doručení počítá jako neúspěšné a zkusí se znovu.
Hromadné operace (bulk) a webhooky
Při hromadném schválení nebo odeslání výkazů (bulk-status) jsou webhooky odesílány efektivně v dávkách s omezeným souběhem — ne synchronně jeden po druhém. To znamená:
- Vaše aplikace může přijmout webhooky prakticky současně (paralelně, limit 10 souběžných)
- Pořadí příjmu není garantováno (záleží na latenci sítě)
- Každý výkaz v dávce generuje samostatný webhook event
Doporučujeme navrhovat endpoint jako idempotentní — bezpečné opakování zpracování stejného eventu (stejné X-MujVykaz-Delivery).