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:
| Vlastnost | Hodnota |
|---|---|
| Metoda | POST |
| Content-Type | application/json |
| Timeout | 10 sekund |
HTTP hlavičky
Každý požadavek obsahuje následující hlavičky:
| Hlavička | Popis | Příklad |
|---|---|---|
Content-Type | Typ obsahu | application/json |
X-Webhook-Id | GUID webhooku | 550e8400-e29b-41d4-a716-446655440000 |
X-Webhook-Event | Typ události | user.created |
X-Webhook-Delivery | Unikátní ID doručení | 660e8400-e29b-41d4-a716-446655440001 |
X-Webhook-Timestamp | Unix timestamp | 1705315800 |
X-Webhook-Signature | HMAC-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í
| Pole | Typ | Popis |
|---|---|---|
event | string | Typ události (např. user.created, ticket.updated) |
entity_id | number | ID entity, které se událost týká |
occurred_at | string | Čas události ve formátu ISO 8601 (UTC) |
data | object | null | Dodatečná data specifická pro daný typ události |
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
-
Validujte podpis – Pokud máte nastavený secret, vždy ověřte
X-Webhook-Signature(viz Zabezpečení) -
Ignorujte neznámá pole – Payload může v budoucnu obsahovat nová pole
-
Zpracujte idempotentně – Použijte
X-Webhook-Deliverypro deduplikaci -
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 dokumentaci | JSON typ | Popis |
|---|---|---|
number | number | Celé číslo nebo desetinné číslo |
string | string | Textový řetězec |
boolean | boolean | true nebo false |
object | object | JSON objekt |
array | array | JSON pole |
null | null | Prá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říklad | Popis |
|---|---|
2024-01-15T10:30:00Z | Datum a čas v UTC |
2024-01-15 | Pouze 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,204nebo 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"}
Pokud váš endpoint neodpoví do 10 sekund, požadavek je považován za neúspěšný a bude opakován.