Přeskočit na hlavní obsah

Formát payloadu

Všechny webhook požadavky používají jednotný formát pro konzistentní zpracování na straně vaší aplikace.

HTTP požadavek

Každý webhook je doručen jako HTTP POST požadavek:

VlastnostHodnota
MetodaPOST
Content-Typeapplication/json
Timeout10 sekund

HTTP hlavičky

Každý požadavek obsahuje následující hlavičky:

HlavičkaPopisPříklad
Content-TypeTyp obsahuapplication/json
X-Webhook-IdGUID webhooku550e8400-e29b-41d4-a716-446655440000
X-Webhook-EventTyp událostiuser.created
X-Webhook-DeliveryUnikátní ID doručení660e8400-e29b-41d4-a716-446655440001
X-Webhook-TimestampUnix timestamp1705315800
X-Webhook-SignatureHMAC-SHA256 podpis*sha256=a1b2c3d4e5f6...

* Hlavička X-Webhook-Signature je přítomna pouze pokud má webhook nastavený secret.

Příklad hlaviček

POST /webhooks/klubero HTTP/1.1
Host: example.com
Content-Type: application/json
X-Webhook-Id: 550e8400-e29b-41d4-a716-446655440000
X-Webhook-Event: user.created
X-Webhook-Delivery: 660e8400-e29b-41d4-a716-446655440001
X-Webhook-Timestamp: 1705315800
X-Webhook-Signature: sha256=a1b2c3d4e5f6789...

Struktura payloadu

Tělo každého webhook požadavku má následující strukturu:

{
"event": "string",
"entity_id": number,
"occurred_at": "string (ISO 8601)",
"data": object | null
}

Popis polí

PoleTypPopis
eventstringTyp události (např. user.created, ticket.updated)
entity_idnumberID entity, které se událost týká
occurred_atstringČas události ve formátu ISO 8601 (UTC)
dataobject | nullDodatečná data specifická pro daný typ události
Formátování

Všechny názvy vlastností používají snake_case (např. entity_id, occurred_at, user_id).

Příklad kompletního požadavku

{
"event": "user.created",
"entity_id": 12345,
"occurred_at": "2024-01-15T10:30:00Z",
"data": {
"user_id": 12345,
"email": "jan.novak@example.com",
"firstname": "Jan",
"surname": "Novák",
"created_at": "2024-01-15T10:30:00Z"
}
}

Hlavička X-Webhook-Delivery

Každé doručení má unikátní identifikátor X-Webhook-Delivery. Tento identifikátor můžete použít pro:

  • Deduplikaci – Stejná událost může být doručena vícekrát (při retry)
  • Debugging – Korelace s logy v Klubero API
  • Idempotence – Zajištění, že událost zpracujete pouze jednou
const processedDeliveries = new Set();

app.post('/webhooks', (req, res) => {
const deliveryId = req.headers['x-webhook-delivery'];

if (processedDeliveries.has(deliveryId)) {
return res.status(200).send('Already processed');
}

processedDeliveries.add(deliveryId);
// ... zpracování události
res.status(200).send('OK');
});

Zpracování payloadu

Best practices

  1. Validujte podpis – Pokud máte nastavený secret, vždy ověřte X-Webhook-Signature (viz Zabezpečení)

  2. Ignorujte neznámá pole – Payload může v budoucnu obsahovat nová pole

  3. Zpracujte idempotentně – Použijte X-Webhook-Delivery pro deduplikaci

  4. Odpovězte rychle – Vraťte HTTP 2xx co nejdříve, náročné operace zpracujte asynchronně

Příklad zpracování (Node.js)

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.KLUBERO_WEBHOOK_SECRET;

app.post('/webhooks/klubero', (req, res) => {
// 1. Ověření podpisu (pokud máte secret)
if (WEBHOOK_SECRET) {
const signature = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);

const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');

if (signature !== expectedSignature) {
return res.status(401).send('Invalid signature');
}
}

// 2. Zpracování události
const { event, entity_id, data } = req.body;
console.log(`Received ${event} for entity ${entity_id}`);

// 3. Asynchronní zpracování
processEventAsync(event, entity_id, data);

// 4. Rychlá odpověď
res.status(200).send('OK');
});

async function processEventAsync(event, entityId, data) {
switch (event) {
case 'user.created':
await handleUserCreated(data);
break;
case 'ticket.created':
await handleTicketCreated(data);
break;
// ... další typy událostí
}
}

app.listen(3000);

Příklad zpracování (C#)

[HttpPost("webhooks/klubero")]
public async Task<IActionResult> HandleWebhook(
[FromBody] WebhookPayload payload,
[FromHeader(Name = "X-Webhook-Signature")] string signature,
[FromHeader(Name = "X-Webhook-Delivery")] string deliveryId)
{
// 1. Ověření podpisu
if (!string.IsNullOrEmpty(_webhookSecret))
{
var body = await GetRequestBodyAsync();
if (!VerifySignature(signature, body))
return Unauthorized("Invalid signature");
}

// 2. Deduplikace
if (await _cache.ExistsAsync($"webhook:{deliveryId}"))
return Ok("Already processed");

await _cache.SetAsync($"webhook:{deliveryId}", "1", TimeSpan.FromHours(24));

// 3. Asynchronní zpracování
_ = ProcessEventAsync(payload);

return Ok();
}

public class WebhookPayload
{
[JsonPropertyName("event")]
public string Event { get; set; }

[JsonPropertyName("entity_id")]
public int EntityId { get; set; }

[JsonPropertyName("occurred_at")]
public DateTime OccurredAt { get; set; }

[JsonPropertyName("data")]
public JsonElement? Data { get; set; }
}

Datové typy

Typ v dokumentaciJSON typPopis
numbernumberCelé číslo nebo desetinné číslo
stringstringTextový řetězec
booleanbooleantrue nebo false
objectobjectJSON objekt
arrayarrayJSON pole
nullnullPrázdná hodnota

Formáty datumů

Všechny datumy a časy jsou ve formátu ISO 8601 v UTC časové zóně:

2024-01-15T10:30:00Z
PříkladPopis
2024-01-15T10:30:00ZDatum a čas v UTC
2024-01-15Pouze datum (pro pole jako birthdate)

Odpověď vašeho endpointu

Úspěšná odpověď

Váš endpoint by měl vrátit:

  • Stavový kód: 200, 201, 202, 204 nebo jakýkoli jiný 2xx kód
  • Tělo: Může být prázdné nebo obsahovat libovolná data (ignorováno)
HTTP/1.1 200 OK
Content-Type: text/plain

OK

Neúspěšná odpověď

Jakýkoli stavový kód mimo rozsah 2xx spustí retry mechanismus:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{"error": "Database connection failed"}
Timeout

Pokud váš endpoint neodpoví do 10 sekund, požadavek je považován za neúspěšný a bude opakován.