> ## Documentation Index
> Fetch the complete documentation index at: https://onlinefoglalas.hu/tudasbazis/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook

> Webhookkal a foglalási események adatait elküldheted egy másik rendszerbe, például a CRM-edbe vagy egy automatizáló eszközbe.

Ez a funkció a Standard csomagtól érhető el.

[Csomagok összehasonlítása →](https://onlinefoglalas.hu/funkciok-arak)

Ha egy másik rendszert is használsz, például egy CRM-et, egy hírlevélküldőt vagy egy automatizáló eszközt (Zapier, Make, n8n), a webhookkal a foglalási események adatai automatikusan oda is eljuthatnak. Ilyenkor egy esemény, például egy új foglalás után a rendszer egy kérést küld az általad megadott címre a foglalás adataival.

A webhook beállításához a fogadó rendszer webhook címe kell. Ezt általában a fogadó rendszer beállításai között találod.

## Webhook felvétele

**1. Nyisd meg a Webhook fület**

A menüben kattints az **Értesítések** menüpontra, majd a **Lista** lehetőségre és válaszd felül a **Webhook** fület.

**2. Kattints az Új értesítés gombra**

Add meg az **Értesítés nevét**, például „CRM: új foglalás” és az **Esemény** mezőben válaszd ki, mikor menjen ki a webhook. Kattints a **Létrehozás** gombra.

**3. Add meg a kérés adatait**

Válaszd ki a kérés módját, add meg a **Webhook URL**-t és az elküldendő adatokat, lásd lent.

**4. Mentsd el**

Kattints a **Mentés** gombra.

**5. Rendelj hozzá Signing Secretet**

A webhook csak akkor megy ki, ha Signing Secret tartozik hozzá. Lásd: [Signing Secret](#signing-secret).

![A Webhook fül a CRM: új foglalás webhookkal, POST móddal, a CRM webhook címével, egy Authorization fejléccel és JSON body-val, amelyben a foglalás adatai kulcsszavakként szerepelnek](https://mintcdn.com/onlinefoglalas-hu/x8YXbsr4i-O8mWkv/tudasbazis/images/webhook/szerkeszto.webp?fit=max\&auto=format\&n=x8YXbsr4i-O8mWkv\&q=85\&s=1a5e5a5e9c6f4619d43731f23046e688)

Egy webhook, ami minden új foglalást elküld a CRM-be

## A kérés beállításai

* **Kérés módja:** **POST**, **PUT**, **GET** vagy **DELETE**. A legtöbb rendszer **POST** kérést vár.
* **Webhook URL:** a fogadó rendszer címe. Csak https\:// kezdetű cím lehet.
* **Headerek:** a kéréshez küldött fejlécek, ha a fogadó rendszer ilyet kér, például egy `Authorization` fejlécet a saját kulcsával. Az **Új header** gombbal adhatsz hozzá újat.
* **Content type:** POST és PUT kérésnél választhatsz a **JSON**, a **Form-data** és a **Nincs body** közül.
* **Body mezők** vagy **JSON body:** az elküldött adatok. Form-data esetén mezőnként adod meg a nevet és az értéket, JSON esetén egy JSON szöveget írsz.

Az URL-be, a fejlécek értékébe és az elküldött adatokba változó adatokat is beszúrhatsz, például a foglalás azonosítóját, az ügyfél nevét vagy a szolgáltatás nevét. A rendszer küldéskor ezeket a valódi adatokkal cseréli ki. JSON-nál a beszúrt értékek akkor sem rontják el a JSON-t, ha például idézőjelet tartalmaznak.

Egy JSON body például így nézhet ki:

```json theme={null}
{
  "foglalas_id": "{appointment_id}",
  "ugyfel": "{customer_full_name}",
  "email": "{customer_email}",
  "szolgaltatas": "{service_name}",
  "idopont": "{appointment_date} {appointment_start_time}"
}
```

A **Beállítások** gombbal () a webhookra is megadhatsz szűrőket, például hogy csak egy adott szolgáltatás foglalásainál menjen ki. Lásd: [Időzítés és szűrők](https://onlinefoglalas.hu/tudasbazis/ertesitesek#időzítés-és-szűrők).

### Teszt küldés

A **Teszt küldés** gombbal azonnal elküldheted a kérést a megadott címre. A rendszer kiírja, milyen HTTP státuszkóddal válaszolt a fogadó rendszer, így rögtön látod, hogy elfogadta-e a kérést.

## Signing Secret

A Signing Secret egy titkos kulcs, amivel a rendszer minden webhook kérést aláír. A fogadó rendszer ebből tudja ellenőrizni, hogy a kérés valóban tőled jött és útközben senki nem módosította. Signing Secret nélkül a webhook nem megy ki, a szerkesztőben ilyenkor figyelmeztetés jelenik meg.

**1. Nyisd meg a Fejlesztőknek oldalt**

A menüben kattints a **Beállítások** menüpontra, majd a **Fejlesztőknek** lehetőségre.

**2. Hozz létre egy kulcsot**

A **Signing Secretek** résznél kattints az **Új Signing Secret** gombra. A **Megnevezés** csak neked szól, például annak a rendszernek a neve, amelyik a kéréseket fogadja.

**3. Rendeld hozzá a webhookokat**

A kulcs sorában kattints a **Webhook** gombra és válaszd ki, mely webhookokat írja alá ez a kulcs. Egy webhookhoz egyszerre egy kulcs tartozhat.

**4. Add meg a kulcsot a fogadó rendszerben**

Másold ki a kulcsot és add meg a fogadó rendszerben, hogy az ellenőrizni tudja az aláírást.

![A Fejlesztőknek oldal egy Minta CRM nevű Signing Secrettel, a kulcs rövidített értékével, a Másolás és a Webhook gombbal](https://mintcdn.com/onlinefoglalas-hu/x8YXbsr4i-O8mWkv/tudasbazis/images/webhook/signing-secret.webp?fit=max\&auto=format\&n=x8YXbsr4i-O8mWkv\&q=85\&s=666ae0d565e0c311485761119ceb7965)

Egy Signing Secret a Fejlesztőknek oldalon

![A Webhook ablak, amelyben a CRM: új foglalás webhook van kiválasztva](https://mintcdn.com/onlinefoglalas-hu/x8YXbsr4i-O8mWkv/tudasbazis/images/webhook/hozzarendeles.webp?fit=max\&auto=format\&n=x8YXbsr4i-O8mWkv\&q=85\&s=41d5f463e3f38febb55ea941a4cb611f)

Webhook hozzárendelése a kulcshoz

Ha úgy gondolod, hogy a kulcs illetéktelen kézbe került, az **Új Secret generálása** lehetőséggel cserélj újat. A régi kulcs azonnal érvénytelenné válik, ezért az újat a fogadó rendszerben is cseréld le.

### Az aláírás ellenőrzése

Minden kérés megkapja az `X-Onlinefoglalas-Signature` fejlécet ebben a formában:

```text theme={null}
X-Onlinefoglalas-Signature: t=1791100000,v1=5f2b...
```

A `t` a küldés ideje Unix időbélyegként, a `v1` pedig a HMAC-SHA256 aláírás hexadecimálisan. Az ellenőrzés lépései:

1. Bontsd fel a fejlécet a `t` és a `v1` értékre.
2. Vedd a kérés nyers body-ját pontosan úgy, ahogy megérkezett. GET és DELETE kérésnél a body üres.
3. Állítsd össze az aláírt szöveget: írd egymás után a `t` értékét, egy pont karaktert (`.`) és a nyers body-t, szóköz nélkül. Például: `1791100000.{"foglalas_id":"1248"}`
4. Számolj erre HMAC-SHA256-ot a Signing Secrettel és kódold hexadecimálisan.
5. Hasonlítsd össze a `v1` értékkel állandó idejű (constant-time) összehasonlítással, például PHP-ban a `hash_equals` függvénnyel. Így az összehasonlítás ideje nem árulja el, hány karakter egyezett.
6. Ellenőrizd, hogy a `t` friss, például legfeljebb 5 perces. Ez véd a replay attack ellen: ha valaki elfog egy kérést, később nem tudja újra elküldeni neked.

### Fontos tudnivalók

* Az aláírás mindig a nyers body-ra készül, a tartalomtípustól függetlenül, tehát JSON és Form-data esetén is.
* GET és DELETE kérésnél nincs body, ilyenkor az aláírt szöveg csak az időbélyeg és utána a pont karakter, például: `1791100000.`
* Ha az aláírás fejléc hiányzik vagy érvénytelen, utasítsd el a kérést, mert nem tőled jött.
* Signing Secret nélkül a webhook ki sem megy, ezért minden hozzád érkező kérés alá van írva.
* A Signing Secretet environment variable-ben vagy secret managerben tárold, ne a forráskódban.

### Példakódok

Az alábbi példák mind ugyanazt csinálják: ellenőrzik az aláírást és az időbélyeget, majd `false` értéket adnak, ha a kérés nem tőled jött vagy túl régi. A példák a Signing Secretet az `ONLINEFOGLALAS_WEBHOOK_SECRET` environment variable-ből olvassák.

```php PHP theme={null}
<?php

function verifyOnlinefoglalasSignature(string $header, string $body, string $secret, int $tolerance = 300): bool
{
    $timestamp = null;
    $signature = null;

    foreach (explode(',', $header) as $part) {
        [$key, $value] = array_pad(explode('=', trim($part), 2), 2, null);

        if ($key === 't') {
            $timestamp = $value;
        }

        if ($key === 'v1') {
            $signature = $value;
        }
    }

    if ($timestamp === null || $signature === null || ! ctype_digit($timestamp)) {
        return false;
    }

    $expected = hash_hmac('sha256', $timestamp . '.' . $body, $secret);

    if (! hash_equals($expected, $signature)) {
        return false;
    }

    return abs(time() - (int) $timestamp) <= $tolerance;
}

$header = $_SERVER['HTTP_X_ONLINEFOGLALAS_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');

if (! verifyOnlinefoglalasSignature($header, $body, getenv('ONLINEFOGLALAS_WEBHOOK_SECRET'))) {
    http_response_code(400);
    exit;
}

$data = json_decode($body, true);
```

```javascript Node.js theme={null}
import crypto from "node:crypto";
import express from "express";

function verifyOnlinefoglalasSignature(header, rawBody, secret, toleranceSeconds = 300) {
    const parts = Object.fromEntries(
        header.split(",").map((part) => {
            const [key, ...rest] = part.trim().split("=");
            return [key, rest.join("=")];
        }),
    );

    const timestamp = parts.t;
    const signature = parts.v1;

    if (!timestamp || !signature || !/^\d+$/.test(timestamp)) {
        return false;
    }

    const expected = crypto
        .createHmac("sha256", secret)
        .update(`${timestamp}.${rawBody}`)
        .digest("hex");

    const a = Buffer.from(expected, "utf8");
    const b = Buffer.from(signature, "utf8");

    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
        return false;
    }

    return Math.abs(Date.now() / 1000 - Number(timestamp)) <= toleranceSeconds;
}

const app = express();

app.post("/webhooks/onlinefoglalas", express.raw({ type: "*/*" }), (req, res) => {
    const rawBody = req.body.toString("utf8");
    const header = req.get("X-Onlinefoglalas-Signature") ?? "";

    if (!verifyOnlinefoglalasSignature(header, rawBody, process.env.ONLINEFOGLALAS_WEBHOOK_SECRET)) {
        return res.sendStatus(400);
    }

    const data = JSON.parse(rawBody);
    res.sendStatus(204);
});
```

```python Python theme={null}
import hashlib
import hmac
import os
import time

from flask import Flask, abort, request


def verify_onlinefoglalas_signature(header: str, raw_body: bytes, secret: str, tolerance: int = 300) -> bool:
    parts = dict(part.strip().split("=", 1) for part in header.split(",") if "=" in part)
    timestamp = parts.get("t")
    signature = parts.get("v1")

    if not timestamp or not signature or not timestamp.isdigit():
        return False

    expected = hmac.new(
        secret.encode("utf-8"),
        timestamp.encode("utf-8") + b"." + raw_body,
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        return False

    return abs(time.time() - int(timestamp)) <= tolerance


app = Flask(__name__)


@app.post("/webhooks/onlinefoglalas")
def onlinefoglalas_webhook():
    header = request.headers.get("X-Onlinefoglalas-Signature", "")

    if not verify_onlinefoglalas_signature(header, request.get_data(), os.environ["ONLINEFOGLALAS_WEBHOOK_SECRET"]):
        abort(400)

    data = request.get_json()
    return "", 204
```

```ruby Ruby theme={null}
require "openssl"

def verify_onlinefoglalas_signature(header, raw_body, secret, tolerance = 300)
  parts = header.to_s.split(",").map { |part| part.strip.split("=", 2) }.select { |pair| pair.size == 2 }.to_h
  timestamp = parts["t"]
  signature = parts["v1"]

  return false if timestamp.nil? || signature.nil? || timestamp !~ /\A\d+\z/

  expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{raw_body}")

  return false unless Rack::Utils.secure_compare(expected, signature)

  (Time.now.to_i - timestamp.to_i).abs <= tolerance
end

# Rails controllerben
def onlinefoglalas_webhook
  valid = verify_onlinefoglalas_signature(
    request.headers["X-Onlinefoglalas-Signature"],
    request.raw_post,
    ENV.fetch("ONLINEFOGLALAS_WEBHOOK_SECRET"),
  )

  return head :bad_request unless valid

  data = JSON.parse(request.raw_post)
  head :no_content
end
```

```go Go theme={null}
package webhook

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"math"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

func VerifyOnlinefoglalasSignature(header string, rawBody []byte, secret string, tolerance time.Duration) bool {
	var timestamp, signature string

	for _, part := range strings.Split(header, ",") {
		key, value, found := strings.Cut(strings.TrimSpace(part), "=")
		if !found {
			continue
		}

		switch key {
		case "t":
			timestamp = value
		case "v1":
			signature = value
		}
	}

	if timestamp == "" || signature == "" {
		return false
	}

	ts, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		return false
	}

	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(timestamp + "."))
	mac.Write(rawBody)
	expected := hex.EncodeToString(mac.Sum(nil))

	if !hmac.Equal([]byte(expected), []byte(signature)) {
		return false
	}

	return math.Abs(time.Since(time.Unix(ts, 0)).Seconds()) <= tolerance.Seconds()
}

func Handler(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	header := r.Header.Get("X-Onlinefoglalas-Signature")
	if !VerifyOnlinefoglalasSignature(header, body, os.Getenv("ONLINEFOGLALAS_WEBHOOK_SECRET"), 5*time.Minute) {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}
```

> [!WARNING]
>
> Az aláírást mindig a nyers body-ra számold, pontosan úgy, ahogy megérkezett. Ha a body-t előbb feldolgozod (például JSON-ként beolvasod és újra szöveggé alakítod), az aláírás nem fog egyezni.

## Kiküldés és hibák

* A webhook a háttérben megy ki, ezért a foglalást nem lassítja.
* A rendszer 10 másodpercig vár a válaszra. Ha a fogadó rendszer nem érhető el, a rendszer háromszor újrapróbálja, 10, 30 és 60 másodperc múlva.
* Ha a fogadó rendszer hibakóddal válaszol (nem 2xx státusszal), a küldés sikertelen lesz és a rendszer nem próbálja újra.

Minden webhook megjelenik az **Elküldött értesítések** oldalon **Webhook** típussal. Ott látod, sikeres volt-e és a sikertelen kérést újra is küldheted. Lásd: [Elküldött értesítések](https://onlinefoglalas.hu/tudasbazis/elkuldott-ertesitesek-megtekintese-es-ujrakuldese).

## Gyakori kérdések

### Hogyan küldhetem át az új foglalásokat a CRM-embe?

Vegyél fel egy webhookot a **Webhook** fülön az **Új időpontfoglalás** eseményre, add meg a CRM webhook címét és a küldendő adatokat, majd rendelj hozzá egy Signing Secretet.

### Miért nem érkezik meg a webhook?

Leggyakrabban azért, mert nincs hozzá Signing Secret rendelve. Nézd meg azt is, hogy a webhook be van-e kapcsolva a listában. Az **Elküldött értesítések** oldalon azt is látod, milyen hibával tért vissza.

### Mi az a Signing Secret és miért kell?

Egy titkos kulcs, amivel a rendszer aláírja a kéréseket. A fogadó rendszer ebből tudja, hogy a kérés valóban tőled jött. Nélküle a webhook nem megy ki.

### Működik a Zapierrel vagy a Make-kel?

Igen, ha a Zapier vagy a Make webhook címét adod meg **Webhook URL**-ként. A kérés ugyanúgy megérkezik, az aláírás ellenőrzése a fogadó oldalon nem kötelező, de ajánlott.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.