Webhook
Bir çalışan okuttuğunda, bir okutma düzeltildiğinde ya da bir izin talep edilip onaylandığında Burdaa sisteminizin adresine anında imzalı bir bildirim gönderir. Ay sonunu ya da gece aktarımını beklemeden ERP veya İK sisteminiz güncel kalır.
Kurulum
Panelde Şirket Ayarları → Entegrasyonlar → Webhook bölümünden bir https adresi ekleyin. Eklerken bir imza anahtarı (whsec_…) bir kez gösterilir; sisteminiz gelen isteğin Burdaa'dan geldiğini bununla doğrular.
- Şirket başına en fazla 5 adres. Yerel ve iç ağ adresleri kabul edilmez.
- Panelden Test Gönder ile
pingolayı gönderebilir, son gönderimlerin durumunu görebilirsiniz. - Her adres için hangi olayların gideceğini seçersiniz: Okutmalar ve/veya İzinler.
- Adresi geçici olarak kapatabilir ya da silebilirsiniz.
- Anahtarı Yenile ile yeni bir imza anahtarı alırsınız. Eski anahtar hemen geçersiz olur; sisteminizdeki anahtarı aynı anda güncelleyin.
Olaylar
| Olay | Ne zaman |
|---|---|
| punch.created | Giriş ya da çıkış okutuldu (yöneticinin elle girdiği kayıt dahil) |
| punch.updated | Yönetici okutmayı düzeltti; data.previous önceki değerleri taşır |
| punch.deleted | Okutma silindi; silinen kaydın son hali gelir |
| leave.created | İzin talep edildi ya da yönetici izin girdi |
| leave.updated | İzin onaylandı, reddedildi ya da tarihi / türü değişti; data.previous önceki değerleri taşır |
| leave.deleted | İzin silindi |
| ping | Panelden gönderilen test |
İstek gövdesi
Her olay adresinize JSON gövdeli bir POST olarak gelir. Olay türü X-Burdaa-Event, gönderim kimliği X-Burdaa-Delivery başlığındadır.
X-Burdaa-Event: punch.created
X-Burdaa-Delivery: 9b1e…
X-Burdaa-Signature: t=1760593202,v1=5f2b…
{
"id": "6f1c…",
"type": "punch.created",
"created_at": "2026-10-16T05:40:02+00:00",
"company_id": "…",
"data": {
"punch_id": "…",
"employee_id": "…",
"employee_code": "1042",
"full_name": "Ayşe Yılmaz",
"type": "check_in",
"method": "qr_dynamic",
"occurred_at": "2026-10-16T05:40:00+00:00",
"location_id": "…",
"location_name": "Kadıköy",
"store_code": "M012"
}
}| Alan | Tür | Açıklama |
|---|---|---|
| id | uuid | Olay kimliği; tekrar denemelerde aynı kalır |
| type | string | Olay türü |
| data.type | string | check_in | check_out |
| data.method | string | qr_dynamic, qr_static (QR) ya da manual (yönetici girişi) |
| data.occurred_at | datetime | Okutma zamanı (UTC) |
| data.employee_code | string | null | Sicil no; kendi sisteminizle eşleştirmek için |
| data.store_code | string | null | Mağaza kodu |
| data.previous | object | Yalnızca punch.updated: önceki type, occurred_at, location_id |
İzin olaylarında data, /leaves uç noktasındaki alanları taşır:
{
"leave_id": "…",
"employee_id": "…",
"employee_code": "1042",
"full_name": "Ayşe Yılmaz",
"type": "annual",
"status": "approved",
"start_date": "2026-11-03",
"end_date": "2026-11-07",
"days": 5,
"deduct_from_pay": false,
"previous": { "type": "annual", "status": "pending", "start_date": "2026-11-03", "end_date": "2026-11-07" }
}Teslim ve tekrar deneme
- Herhangi bir
2xxcevap "ulaştı" sayılır. Cevabı 10 saniye içinde verin; ağır işi kuyruğa alıp hemen dönün. - Diğer cevaplarda ya da zaman aşımında 1 dk, 5 dk, 15 dk, 1 sa, 3 sa, 6 sa ve 12 sa sonra tekrar denenir (toplam 8 deneme, yaklaşık 1 gün).
- Aynı olay birden fazla gelebilir: gövdedeki
idile çift kaydı eleyin. Olayların sırası garanti değildir; sıralama içinoccurred_atkullanın. - Tüm denemeler biten gönderim panelde "Ulaşmadı" olarak görünür; Tekrar gönder ile baştan gönderebilirsiniz. Günlerce ulaşılamayan adres otomatik kapatılır.
- Kaçırılan olayları /punches ve /leaves ile telafi edebilirsiniz.
İmza doğrulama
X-Burdaa-Signature başlığında t gönderim zamanı (unix saniye), v1 ise t + "." + ham gövde metninin imza anahtarıyla HMAC-SHA256 özetidir (hex). Gövdeyi JSON olarak ayrıştırmadan, geldiği haliyle doğrulayın; 5 dakikadan eski t değerlerini reddedin.
import crypto from "node:crypto"
function verify(rawBody, header, secret) {
const [, t, v1] = header.match(/^t=(\d+),v1=([0-9a-f]+)$/) ?? []
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex")
return v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}import hmac, hashlib, re, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]+)", header)
if not m or abs(time.time() - int(m[1])) > 300:
return False
expected = hmac.new(secret.encode(), m[1].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(m[2], expected)using System.Security.Cryptography;
using System.Text;
static bool Verify(string rawBody, string header, string secret)
{
var parts = header.Split(',');
var t = parts[0].Substring(2);
var v1 = parts[1].Substring(3);
if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(t)) > 300) return false;
using var h = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var expected = Convert.ToHexString(h.ComputeHash(Encoding.UTF8.GetBytes($"{t}.{rawBody}"))).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(v1), Encoding.UTF8.GetBytes(expected));
}