Webhookok
A webhookokkal valós időben értesülhetsz az OkosMunkalapban történt eseményekről, anélkül hogy ismételten le kellene kérdezned az API-t. Amikor egy munkalap státusza változik vagy egy ügyfél módosul, az OkosMunkalap egy aláírt HTTP POST kérést küld az általad megadott címre.
Beállítás
Szekció neve “Beállítás”A webhookok kulcsonként konfigurálhatók a Beállítások → Integrációk → Kulcsok oldalon:
- Add meg a webhook címet (a saját szervered végpontja).
- Válaszd ki, mely eseményekre szeretnél értesítést kapni.
- Amikor egy kulcsra webhook címet állítasz be úgy, hogy addig nem volt aláíró titka, a rendszer generál egy aláíró titkot (
whsec_előtaggal). Ez csak egyszer jelenik meg, ezért azonnal mentsd el. Ezzel ellenőrzöd majd a beérkező kérések hitelességét. (Ha a címet eltávolítod, a titok is törlődik; új cím beállításakor új titok keletkezik.)
A webhook címmel szembeni követelmények:
- kizárólag
https://, - a 443 vagy a 8443 port (port nélkül a 443 az alapértelmezett),
- nem tartalmazhat beágyazott hitelesítő adatot (
felhasznalo:jelszo@host), - nem mutathat belső, privát, loopback vagy felhő metadata címre.
A cím eltávolításakor a hozzá tartozó események és az aláíró titok is törlődnek.
Az esemény formátuma
Szekció neve “Az esemény formátuma”Minden esemény ugyanazt a JSON szerkezetet kapja a kéréstörzsben:
| Mező | Típus | Leírás |
|---|---|---|
id |
string | az esemény egyedi azonosítója (evt_...); használd idempotenciára |
type |
string | az eseménytípus, pl. worksheet.completed |
webhookVersion |
string | a payload verziója (dátum alapú, pl. 2026-06-15) |
createdAt |
string | az esemény keletkezésének ideje, ISO 8601 UTC |
data |
objektum | az érintett entitás (munkalap vagy ügyfél) |
A data mező ugyanazt a szerkezetet tartalmazza, mint a megfelelő REST GET válasz data mezője (lásd az API referenciát). Így pull és push módon is ugyanazokat a mezőket kapod. A csak írható mezők (pl. az ügyfél notes, a munkalap notes / internalNotes / workAddress) sem a REST válaszban, sem a webhook data mezőjében nem jelennek meg.
Példa egy munkalap lezárásáról:
{ "id": "evt_3f8a2c9b1e4d5a6f7089abcdef012345", "type": "worksheet.completed", "webhookVersion": "2026-06-15", "createdAt": "2026-06-15T09:42:13.123Z", "data": { "id": "ws_abc123", "worksheetNumber": "ML-2026-0042", "status": "completed", "customerId": "abc123", "customerName": "Példa Kft.", "title": "Riasztó hibajavítás", "subtotal": 26000, "vatAmount": 5528, "total": 26000, "createdAt": { "_seconds": 1781514000, "_nanoseconds": 0 }, "url": "https://app.okosmunkalap.hu/worksheets/ws_abc123" }}Figyelj a két
createdAtkülönbségére: a payload felső szintűcreatedAtmezője ISO 8601 string (az esemény ideje), míg adata.createdAtFirestore időbélyeg objektum (_seconds/_nanoseconds), mint a REST válaszokban. Lásd: Dátum és idő formátum.
A kérés fejlécei
Szekció neve “A kérés fejlécei”| Fejléc | Leírás |
|---|---|
X-OkosMunkalap-Signature |
az aláírás, t=<időbélyeg>,v1=<hex> formátumban |
X-OkosMunkalap-Event |
az eseménytípus (mint a payload type mezője) |
X-OkosMunkalap-Delivery |
az esemény azonosítója (mint a payload id mezője) |
Content-Type |
application/json |
Eseménytípusok
Szekció neve “Eseménytípusok”| Esemény | Mikor keletkezik |
|---|---|
worksheet.created |
új munkalap jött létre |
worksheet.status_changed |
a munkalap státusza megváltozott (minden státuszváltásnál) |
worksheet.completed |
a munkalap completed státuszba került |
worksheet.invoiced |
a munkalap invoiced státuszba került |
customer.created |
új ügyfél jött létre |
customer.updated |
egy ügyfél érdemi adata módosult |
Néhány fontos szabály:
- A
completedstátuszra váltás két eseményt küld:worksheet.status_changedésworksheet.completed. Ugyanígy azinvoicedátmenetworksheet.status_changedésworksheet.invoicedeseményt is ad. Ha mindkét típusra feliratkoztál, mindkettőt megkapod, különid-vel. - A
customer.updatedcsak akkor tüzel, ha valódi tartalmi mező változik (név, telefon, email, cég, adószám, adókategória, cím, külső azonosítók, egyedi mezők, VIP jelölés). A belső, származtatott mezők (pl. statisztikák) változása nem küld eseményt. - Törlésről jelenleg nincs esemény.
- A
partially_invoicedállapotra váltás csakworksheet.status_changedeseményt ad.
Az aláírás ellenőrzése
Szekció neve “Az aláírás ellenőrzése”Minden kérés alá van írva HMAC-SHA256 algoritmussal, a kulcs aláíró titkával. Ellenőrizd minden beérkező kérésnél, mielőtt feldolgozod.
Az X-OkosMunkalap-Signature fejléc formátuma:
t=1781600400,v1=5257a869e9...t: az aláírás időbélyege (Unix epoch másodperc),v1: a hexadecimális aláírás.
Az ellenőrzés lépései:
- Olvasd ki a nyers kéréstörzset (ne alakítsd át, ne serializáld újra).
- Bontsd fel a fejlécet
tésv1részre. - Állítsd össze az aláírandó szöveget:
t+.(pont) + a nyers törzs. - Számítsd ki a HMAC-SHA256 értéket az aláíró titokkal, hexben.
- Hasonlítsd össze a számított értéket a
v1-gyel, lehetőleg időállandó (constant-time) összehasonlítással.
Node.js:
const crypto = require('crypto');
// toleranceSec: a replay-ablak másodpercben (alapértelmezés 5 perc)function verifySignature(rawBody, signatureHeader, secret, toleranceSec = 300) { if (!signatureHeader) return false;
const parts = {}; for (const piece of signatureHeader.split(',')) { const i = piece.indexOf('='); if (i > 0) parts[piece.slice(0, i)] = piece.slice(i + 1); } const { t, v1 } = parts; if (!t || !v1) return false;
// Replay-ablak: a túl régi vagy jövőbeli időbélyeg elutasítása const age = Math.floor(Date.now() / 1000) - Number(t); if (!Number.isFinite(age) || Math.abs(age) > toleranceSec) return false;
const expected = crypto .createHmac('sha256', secret) .update(`${t}.${rawBody}`) .digest('hex');
const a = Buffer.from(expected, 'hex'); const b = Buffer.from(v1, 'hex'); // A timingSafeEqual eltérő hosszra kivételt dob, ezért előbb hosszt ellenőrzünk return a.length === b.length && crypto.timingSafeEqual(a, b);}PHP:
// $toleranceSec: a replay-ablak másodpercben (alapértelmezés 5 perc)function verifySignature(string $rawBody, string $signatureHeader, string $secret, int $toleranceSec = 300): bool { parse_str(str_replace(',', '&', $signatureHeader), $parts); $t = $parts['t'] ?? ''; $v1 = $parts['v1'] ?? ''; if ($t === '' || $v1 === '') return false;
// Replay-ablak: a túl régi vagy jövőbeli időbélyeg elutasítása if (abs(time() - (int) $t) > $toleranceSec) return false;
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret); return hash_equals($expected, $v1);}A példák egy ötperces replay-ablakot alkalmaznak: a túl régi vagy jövőbeli időbélyegű kérést elutasítják (ez a visszajátszásos támadások ellen véd). A tolerancia mértékét a toleranceSec paraméterrel állíthatod a saját igényed szerint.
Idempotencia
Szekció neve “Idempotencia”Ugyanaz az esemény több okból is megérkezhet kétszer (pl. újrapróbálás hálózati hiba után). Minden eseménynek van egy stabil id mezője (ugyanez az X-OkosMunkalap-Delivery fejléc). Tárold a feldolgozott esemény azonosítókat, és ha egy id-t már láttál, hagyd ki a kérést. Újrapróbáláskor az id változatlan marad, így a deduplikáció megbízható.
Az értesítés feldolgozása és újrapróbálás
Szekció neve “Az értesítés feldolgozása és újrapróbálás”A végpontod akkor számít sikeresnek, ha 2xx HTTP választ ad. Minden más válasz (vagy időtúllépés, hálózati hiba) újrapróbálást vált ki.
- Válaszolj gyorsan. Adj vissza
2xx-et, amint átvetted az eseményt, és a tényleges feldolgozást végezd a háttérben. A kéréshez 10 másodperc időkorlát tartozik. - Újrapróbálás: sikertelen kézbesítést a rendszer legfeljebb 16 alkalommal, körülbelül 3 napon át próbál újra, növekvő (exponenciális) várakozással.
- Nincs átirányítás követés: a webhook cím legyen a végleges célpont (a
3xxválaszokat nem követjük). - Végső kudarc: ha minden próbálkozás elbukik, a kézbesítés
failedállapotba kerül. Ezeket a Beállítások → Integrációk → Webhookok felületen megtekintheted és kézzel újraküldheted (a tárolt esemény 90 napig elérhető).
Néhány eset azonnal véglegesnek számít (nincs újrapróbálás): ha a kulcsot időközben visszavonták vagy törölték, illetve ha a webhook cím biztonsági ellenőrzése elbukik.
Verziózás
Szekció neve “Verziózás”A payload szerkezetét a webhookVersion mező jelöli (dátum alapú, mint az API verzió). Új mezők a meglévő verzión belül is megjelenhetnek, ezért a feldolgozásod legyen elnéző az ismeretlen mezőkkel szemben.