Tovább a tartalomhoz
ÁrakAlkalmazás

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.

A webhookok kulcsonként konfigurálhatók a Beállítások → Integrációk → Kulcsok oldalon:

  1. Add meg a webhook címet (a saját szervered végpontja).
  2. Válaszd ki, mely eseményekre szeretnél értesítést kapni.
  3. 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.

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 createdAt különbségére: a payload felső szintű createdAt mezője ISO 8601 string (az esemény ideje), míg a data.createdAt Firestore időbélyeg objektum (_seconds / _nanoseconds), mint a REST válaszokban. Lásd: Dátum és idő formátum.

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é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 completed státuszra váltás két eseményt küld: worksheet.status_changed és worksheet.completed. Ugyanígy az invoiced átmenet worksheet.status_changed és worksheet.invoiced eseményt is ad. Ha mindkét típusra feliratkoztál, mindkettőt megkapod, külön id-vel.
  • A customer.updated csak 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 csak worksheet.status_changed eseményt ad.

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:

  1. Olvasd ki a nyers kéréstörzset (ne alakítsd át, ne serializáld újra).
  2. Bontsd fel a fejlécet t és v1 részre.
  3. Állítsd össze az aláírandó szöveget: t + . (pont) + a nyers törzs.
  4. Számítsd ki a HMAC-SHA256 értéket az aláíró titokkal, hexben.
  5. 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.

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 3xx vá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.

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.