API Referansı
Bu özellik uygulamada hâlâ Yakında olarak işaretli: şu anda API anahtarı veya webhook oluşturamazsın. Ayarlar > API ve Webhook'lar sayfasını açarsan sadece özelliğin yolda olduğuna dair bir bildirim görürsün. Bu sayfa, özellik kullanıma açıldığında nasıl çalışacağını açıklar.
Bu sayfa, Salonnare API'sine karşı bir entegrasyon oluşturan geliştiriciler için teknik referanstır. API anahtarının ve webhook'un ne olduğunu zaten bildiğini varsayar; sade bir anlatım ve bunları oluşturacağın ekranları arıyorsan API ve Webhook'lar sayfasına bak.
Aşağıdaki her iddia doğrudan Salonnare sunucu kaynak kodundan alınmıştır. Kod ile bu sayfa herhangi bir noktada çelişirse koda güven; bu belge eskiyebilir, gerçekten çalışan şey koddur.
Kimlik doğrulama
API anahtarını her istekte X-API-Key başlığında gönder:
curl https://<your-salon>.salonnare.com/api/bookings \
-H "X-API-Key: sk_live_a1b2_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Anahtar biçimi
Bir anahtar şu şekilde görünür: sk_live_<4 onaltılık karakter>_<48 onaltılık karakter>, örneğin:
sk_live_a1b2_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Sadece ilk 12 karakter (sk_live_ artı 4 karakterlik bölüm) oluşturulduktan sonra saklanır veya yeniden gösterilir. Kalan 48 karakter gerçek gizli kısımdır. Salonnare tam anahtarın kendisini değil, SHA-256 özetini saklar.
Anahtar edinme
Anahtarlar ve webhook'lar uygulamada Ayarlar -> API ve Webhook'lar altında bir salon yöneticisi tarafından yönetilir. Bir anahtar oluştururken üç yetki seviyesinden birini seçersin:
| Yetki | Bugün izin verdiği |
|---|---|
read (varsayılan) | Sadece GET/HEAD/OPTIONS. Herhangi bir uç noktada başka bir yöntem 403 döner. |
write | Pratikte read ile aynı: hiçbir uç nokta şu anda bir API anahtarı üzerinden yazma isteği kabul etmiyor (aşağıdaki Okuma uç noktaları bölümüne bak; altı rotanın tamamı GET). |
full | Aynı sebeple bugün read/write ile aynı. |
Başka bir deyişle: yazma erişimi henüz yok, anahtarın hangi yetki seviyesinde olduğu önemli değil. API üzerinden randevu, müşteri, hizmet veya personel oluşturmayı ya da değiştirmeyi bekleyen bir entegrasyon kurma; bir API anahtarı kabul eden her rota salt okunur, ve GET dışında herhangi bir şey deneyen bir istek rota mantığına ulaşmadan reddedilir.
Tam anahtar tam olarak bir kez gösterilir, anahtar oluşturma çağrısının yanıtında, onu oluşturduğun anda. Hemen sakla (bir sohbet mesajında veya e-tabloda değil, bir gizli bilgi yöneticisinde); Salonnare onu sana bir daha gösteremez. Kaybedersen, iptal et ve yeni bir anahtar oluştur.
Anahtarın hangi salonla konuştuğu
Salonu (tenant'ı) belirleyen anahtarın kendisidir, çağırdığın ana bilgisayar adı değil. İsteklerini kendi salonunun alt alan adına gönderebilirsin (https://<your-salon>.salonnare.com/api/...); bu önerilen, belirsizliğe yer bırakmayan yoldur, ama yanıttaki salon her zaman anahtarın ait olduğu salondur, hangi geçerli Salonnare host'una isteği gönderdiğinden bağımsız olarak.
Kimlik doğrulama hataları
| Durum | Durum kodu | Gövde |
|---|---|---|
X-API-Key başlığı ve Authorization: Bearer token'ı yok | 401 | {"error": "Token ontbreekt.", "code": "unauthorized", "error_key": "server.errors.token_missing"} |
| Bilinmeyen, hatalı biçimli veya süresi geçmiş anahtar | 401 | {"error": "Invalid API key.", "code": "invalid_api_key"} |
| Anahtarın salonu aktif değil (örneğin askıya alınmış) | 403 | {"error": "Salon is not active.", "code": "tenant_inactive"} |
Bir read anahtarıyla GET/HEAD/OPTIONS dışında bir istek | 403 | {"error": "This API key is read-only.", "code": "read_only_key"} |
API anahtarı taşıyan bir istek İngilizce hata mesajları alır: yukarıdaki tablodaki üç anahtar katmanı hatası her zaman İngilizcedir, çeviri mekanizmasından geçen hatalar (error_key alanına sahip olanların çoğu) ise hangi Accept-Language değerini gönderirsen gönder anahtarlı trafik için İngilizce döner. Anahtarsız bir istek (ilk satır) Salonnare'ın desteklediği dokuz dil için (nl, en, de, fr, es, it, pl, pt, tr) Accept-Language başlığını takip eder, diğer durumlarda Hollandacadır. Diğer uç noktalardaki bazı mesajlar hâlâ sabit Hollandaca metinlerdir. Her ikisine de güvenme: entegrasyonunu HTTP durum koduna (ve varsa code/error_key'e) göre dallandır, mesaj metnine göre değil. Gövdeler her zaman error içerir; birkaç rota code ekler, ve bazıları bunun üzerine error_key/error_params ekler; esnek bir şekilde ayrıştır ve kesin, sabit bir anahtar kümesi olduğunu varsayma.
Okuma uç noktaları
Bir API anahtarı tam olarak altı uç noktaya okuma erişimi verir. Anahtarın yetki seviyesi ne olursa olsun başka hiçbir rota bir API anahtarı kabul etmez:
| Yöntem | Yol | Döndürdüğü |
|---|---|---|
GET | /api/bookings | Randevu listesi |
GET | /api/bookings/:id | Bir randevu |
GET | /api/crm/clients | Müşteri listesi |
GET | /api/crm/clients/:id | Bir müşteri |
GET | /api/services | Hizmet listesi |
GET | /api/staff | Personel listesi |
Salonnare yönetim uygulamasının diğer kısımlarının aksine, bu altı rota bir API anahtarına ham veritabanı satırını döndürmez. Her nesne türü, sabit, sürümlenmiş bir alan listesini snake_case olarak her zaman yayan sabit bir şekillendirme fonksiyonundan geçer: toApiClient, toApiStaff, toApiBooking, toApiService. Altta yatan kayıt başka ne içerse veya gelecekte ne kazanırsa kazansın, bu listenin dışındaki hiçbir şey asla dahil edilmez. Dört şekilden ikisi, webhook karşılığıyla kasıtlı olarak alan alan aynıdır: toApiClient, client.created/client.updated yüküyle eşleşir, ve toApiBooking, services dizisi de dahil olmak üzere randevu yüküyle eşleşir (ikisi de hizmet satırları için aynı izin listesini kullanır). Salonnare webhook'larını zaten ayrıştırıyorsan, müşteri ve randevu REST yanıtları için ayrı bir ayrıştırıcıya ihtiyaç yoktur.
Bu filtreleme sadece API anahtarı trafiğine uygulanır. Bir salonun kendi oturum açmış personeli, bir X-API-Key başlığı yerine bir Bearer token'ıyla aynı URL'leri çağırdığında, uygulamanın kendisinin dayandığı tam, filtrelenmemiş kaydı almaya devam eder (bu şekillendiricilerin kasıtlı olarak çıkardığı iç alanları da içerir). Fark tamamen erişim kanalındadır, uç nokta veya veride değil. Bu belge sadece X-API-Key şeklini açıklar; entegrasyonunun göreceği şey bu.
GET /api/bookings
Sorgu parametreleri (tümü isteğe bağlı):
| Parametre | Biçim | Etki |
|---|---|---|
from, to | YYYY-MM-DD | Randevu başlangıç zamanı üzerinde yarı açık bir tarih aralığı. |
date | YYYY-MM-DD | Tek bir gün (from=to=o gün ile aynı etki). |
source | metin | Randevunun nasıl oluşturulduğuna göre filtrele (manual, online, google_reserve, ...). |
location_id | tam sayı | Tek bir şubeye göre filtrele. |
client_id | tam sayı | Bir müşterinin randevularına göre filtrele. |
page | tam sayı | Sayfalamayı etkinleştir (aşağıya bak). Belirtilmezse tüm dizi döner. |
limit | tam sayı | page ayarlıyken sayfa boyutu. Varsayılan 50, maksimum 200. |
?page= olmadan yanıt, en fazla 2000 satırla sınırlı, düz bir JSON dizisidir ve varsayılan olarak en yeniden en eskiye sıralanır; ancak hem from hem to ayarlandığında veya date ayarlandığında en eskiden en yeniye (kronolojik olarak) sıralanır. ?page=N ile yanıt şu şekildedir:
{
"items": [ /* same objects as below */ ],
"total": 134,
"page": 1,
"limit": 50,
"pages": 3
}
Her iki modda da her öğe toApiBooking()'in çıktısıdır:
{
"id": 4821,
"client_id": 312,
"staff_id": 7,
"start_at": "2026-08-25T09:00:00.000Z",
"end_at": "2026-08-25T09:45:00.000Z",
"status": "scheduled",
"notes": "",
"services": [
{
"service_id": 41,
"staff_id": 7,
"duration_min": 45,
"price": "45.00"
}
]
}
Bu uç nokta için tam alan kümesi budur; müşteri adı, oda bilgisi veya randevu seviyesinde bir toplam yoktur. status, scheduled, provisional, completed, cancelled, no-show değerlerinden biridir. İptal edilen ve gelmeyen randevular listeye dahildir; sadece aktif randevuları istiyorsan status üzerinden istemci tarafında filtrele. notes her zaman bir metindir (gösterilecek bir şey olmadığında null değil, boş metin).
services, randevunun hizmetlerini, yapıldıkları sırayla içerir ve randevu webhook'larının gönderdiği aynı dizidir. Her satırda: service_id (adı çözümlemek için GET /api/services'e bak), staff_id (o belirli hizmeti yapan personel, randevunun staff_id'sinden farklı olabilir), duration_min ve price; fiyat, randevu oluşturulduğunda ondalık bir metin olarak anlık görüntü alınmıştır, bu yüzden hizmette sonradan yapılan bir fiyat değişikliği geçmişi yeniden yazmaz. Bir randevunun her zaman en az bir satırı vardır.
GET /api/bookings/:id
Sorgu parametresi yok. Yukarıdaki liste öğesiyle tam olarak aynı şekilde tek bir nesne döndürür:
{
"id": 4821,
"client_id": 312,
"staff_id": 7,
"start_at": "2026-08-25T09:00:00.000Z",
"end_at": "2026-08-25T09:45:00.000Z",
"status": "scheduled",
"notes": "",
"services": [
{
"service_id": 41,
"staff_id": 7,
"duration_min": 45,
"price": "45.00"
}
]
}
Burada müşteri adı, oda veya kür (cure) bilgisi yoktur; API anahtarı yanıtı kasıtlı olarak yukarıdaki sekiz alanda durur, oysa bu uç noktanın oturum açmış kullanıcı sürümü, satır başına oda detayı, hizmet başına ek personel ve randevunun kür bağlamıyla çok daha zengin bir nesne döndürür. services sadece gerçek hizmetleri taşır: uygulamanın bekleme süresi için kullandığı geçiş satırları (örneğin boyanın işlemesi) dışarıda tutulur, böylece dizi randevu webhook'uyla tam olarak eşleşir. İleride alan eklemek kırıcı bir değişiklik değildir, ama bugünkü yanıtın tam olarak bu şekilde olduğunu varsay.
Sayısal olmayan veya pozitif olmayan bir :id, {"error": "Invalid appointment ID.", "code": "bad_request", "error_key": "server.errors.invalid_booking_id_upper"} ile 400 döndürür. Geçerli ama var olmayan bir :id, {"error": "Appointment not found.", "code": "not_found", "error_key": "server.errors.booking_not_found"} ile 404 döndürür.
GET /api/crm/clients
Sorgu parametreleri (tümü isteğe bağlı):
| Parametre | Biçim | Etki |
|---|---|---|
search | metin | Ad, tercih edilen ad, e-posta ve telefonla eşleşir. |
label_id | tam sayı | Bu etiketin atandığı müşterilere göre filtrele. |
page | tam sayı | Sayfalamayı etkinleştir, yukarıdaki randevularla aynı zarf. |
limit | tam sayı | page ayarlıyken sayfa boyutu. Varsayılan 50, maksimum 200. |
?page= olmadan yanıt, alfabetik olarak sıralanmış düz bir JSON dizisidir (search ayarlıyken arama ilgisine göre). Her öğe toApiClient()'in çıktısıdır:
{
"id": 312,
"name": "Anna Voorbeeld",
"email": "anna@example.com",
"phone": null,
"mobile": "+31612345678",
"first_name": "Anna",
"last_name": "Voorbeeld",
"preferred_name": null,
"city": "Waalwijk",
"country": "NL",
"created_at": "2026-01-14T10:22:00.000Z"
}
Bu tam alan kümesidir; adres, doğum tarihi, etiketler, sadakat veya gelmeme verisi yoktur, ve eskiden buradan sızan hesap/ödeme sağlayıcısı alanları (passwordHash, stripeCustomerId ve benzerleri) da yoktur. search/label_id hangi müşterilerin eşleştiğini filtreler; her biri hakkında döndürülen bilgiye alan eklemez.
GET /api/crm/clients/:id
Sorgu parametresi yok. Yukarıdaki liste öğesiyle tam olarak aynı şekilde tek bir nesne döndürür:
{
"id": 312,
"name": "Anna Voorbeeld",
"email": "anna@example.com",
"phone": null,
"mobile": "+31612345678",
"first_name": "Anna",
"last_name": "Voorbeeld",
"preferred_name": null,
"city": "Waalwijk",
"country": "NL",
"created_at": "2026-01-14T10:22:00.000Z"
}
Ön görüşme formu yanıtları (intake_responses), uygulamada bunları görüntülemeye kim yetkili olursa olsun, bir API anahtarı için bu yanıtın hiçbir zaman parçası olmaz; alan basitçe yukarıdaki sabit listede yoktur.
Sayısal olmayan veya sıfır/NaN bir :id, {"error": "Invalid client ID.", "code": "bad_request", "error_key": "server.errors.invalid_client_id"} ile 400 döndürür. Geçerli ama var olmayan bir :id, {"error": "Client not found.", "code": "not_found", "error_key": "server.errors.not_found", "error_params": {"entity": "Client"}} ile 404 döndürür.
GET /api/services
Sorgu parametreleri:
| Parametre | Biçim | Etki |
|---|---|---|
bookable | 1 veya true | Sadece aktif (arşivlenmemiş) hizmetler. Belirtilmezse arşivlenmiş hizmetler de dahil edilir. |
Yanıt her zaman düz bir JSON dizisidir (bu uç noktada sayfalama desteği yok). Her öğe toApiService()'in çıktısıdır:
[
{
"id": 41,
"name": "Knipbeurt",
"duration_min": 45,
"price": "45.00",
"category_id": 3,
"active": true
}
]
Bu tam alan kümesidir; açıklama, ürün kodu, KDV oranı, planlama sınırlamaları yoktur, ve kategori adı/rengi yoktur (sadece ham category_id; kategorinin detaylarına ihtiyacın varsa ayrıca al veya kendi eşleştirmeni tut).
GET /api/staff
Sorgu parametresi yok. Yanıt her zaman düz bir JSON dizisidir (sayfalama desteği yok). Her öğe toApiStaff()'in çıktısıdır:
[
{
"id": 7,
"name": "Chris Medewerker",
"email": "chris@example.com",
"role": "staff"
}
]
Bu tam alan kümesidir; iban/bic, Stripe Connect durumu, kilitlenme/giriş denemesi durumu veya sayfa yetki bayrakları yoktur. Entegrasyonun ödeme detaylarına ihtiyacı varsa, bu bir API anahtarının okuyabileceği bir şey değildir; doğrudan salondan gelmesi gerekir.
Kasıtlı olarak active alanı yoktur: uç nokta sadece salonun aktif personelini döndürür, bu yüzden alan hiçbir zaman true dışında bir şey olamaz. Birini aşamalı olarak çıkarmak için bir active: false bekleme; ayrılan bir personel bu listede görünmeyi basitçe bırakır, bu yüzden "yanıtta eksik" olmasını sinyal olarak kabul et.
Hız sınırı
Her /api/* isteği, API anahtarı istekleri de dahil, salon ve IP adresi birlikte başına dakikada 200 istek ile sınırlıdır. Altı okuma uç noktasından hiçbirinin bunun üzerine ek, daha sıkı bir sınırlayıcısı yoktur.
Bu sınırı aştığında şunları alırsın:
- Durum
429 - Gövde:
{"error": "Too many requests, please try again later", "code": "rate_limited", "error_key": "server.errors.rate_limited_comma"} - Her yanıtta (sadece sınırlandığında değil) başlıklar:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset(Unix zaman damgası, saniye) 429'un kendisinde, yeniden denemeden önce beklenecek saniye sayısıyla ek birRetry-Afterbaşlığı
Sabit bir aralıkta yoklamak yerine Retry-After'a uy; bu, çarptığın pencerenin gerçek sıfırlanma zamanını yansıtır.
Webhook'lar
Yoklama yapmak yerine bir URL kaydedebilir ve olaylar gerçekleştiğinde Salonnare'ın bunları o URL'ye göndermesini sağlayabilirsin. Webhook'lar uygulamada Ayarlar -> API ve Webhook'lar -> Webhook'lar altında yapılandırılır, API anahtarlarının bulunduğu aynı yer.
Zarf
Her teslimat şu JSON gövdesine sahip bir POST'tur:
{
"event": "booking.created",
"data": { /* event-specific payload, see below */ },
"timestamp": "2026-08-25T09:00:03.512Z"
}
Gövdenin yanında, her teslimat iki başlık taşır: X-Webhook-Signature (İmzaları doğrulama bölümüne bak) ve X-Webhook-Event (gövdedeki event ile aynı değer).
Olaylar
Altı olayın herhangi bir kombinasyonuna abone olabilirsin. "Ne zaman tetiklenir" sütununu harfiyen oku; bu olayların her biri sadece uygulamadaki bir salon personelinin eyleminden tetiklenir, ve her birinin sunucuda tam olarak bir tetikleme noktası vardır:
| Olay | Ne zaman tetiklenir |
|---|---|
booking.created | Bir personel uygulamada bir randevu oluşturduğunda (POST /api/bookings) |
booking.updated | Bir personel uygulamada bir randevuyu düzenlediğinde (PATCH/PUT /api/bookings/:id); tamamlandı veya gelmedi durumuna geçiş de buna dahildir, çünkü uygulama bunları aynı çağrı üzerinden yapar |
booking.cancelled | Bir personel uygulamada bir randevuyu iptal ettiğinde (DELETE /api/bookings/:id) |
payment.received | Bir personel kasada bir ödemeyi tamamladığında (POST /api/pos/orders) |
client.created | Bir personel uygulamada bir müşteri oluşturduğunda (POST /api/crm/clients) |
client.updated | Bir personel uygulamada bir müşteriyi düzenlediğinde (PATCH/PUT /api/crm/clients/:id) |
Hangi şey bir olayı tetiklemez
Entegrasyonları yakalayan kısım burası, bu yüzden ima edilmek yerine açıkça yazıldı. Randevular, müşteriler ve ödemeler başka herhangi bir şekilde ortaya çıktığında hiçbir webhook üretilmez. Somut olarak, şu durumlarda hiçbir şey gönderilmez:
- bir müşteri salonun randevu sayfası üzerinden online randevu aldığında, veya orada kendisi bir randevuyu iptal ettiğinde ya da onayladığında;
- bir randevu Google Reserve üzerinden geldiğinde;
- bir bekleme listesi girişi bir randevuya dönüştürüldüğünde;
- tekrarlayan bir seri kendi randevularını oluşturduğunda;
- bir kür (
cure) serisi oluşturulduğunda, veya bir kürün bir seansı iptal edildiğinde; - geçici bir randevu, kapora son tarihi geçtiği için otomatik olarak iptal edildiğinde veya kapora ödendiği için onaylanmış olarak yükseltildiğinde;
- bir randevu, kasa ödemesinin yan etkisi olarak tamamlandı olarak işaretlendiğinde;
- bir müşteri kendini randevu sayfasında oluşturduğunda, Google/Microsoft/Apple/Facebook üzerinden kayıt olduğunda veya bir içe aktarma ile oluşturulduğunda;
- bir müşteri kaydı, müşteri düzenleme ekranı dışında herhangi bir şeyle değiştiğinde; sadakat kademesi yeniden hesaplama, bakiye, ön görüşme formu yanıtları, geri dönen bir e-posta, veya bir silme talebiyle anonimleştirildiğinde.
Pratik sonuç: webhook'ları eksiksiz bir değişiklik akışı olarak kullanamazsın. Online randevu kabul eden bir salon için, yeni randevuların çoğu uç noktana hiç ulaşmayacaktır. Periyodik olarak bir from/to penceresiyle GET /api/bookings üzerinden mutabakat sağla ve webhook'ları bunun üzerine düşük gecikmeli bir ipucu olarak değerlendir, gerçeğin kaynağı olarak değil.
Altı yükün tamamı sunucuda sabit bir alan izin listesinden geçer; sadece aşağıda listelenen alanlar, altta yatan kayıt başka ne içerse içersin, her zaman snake_case olarak gönderilir. Bu kasıtlı bir sözleşmedir: webhook yükleri veritabanından sessizce yeni alanlar kazanmaz.
booking.created / booking.updated / booking.cancelled:
{
"event": "booking.created",
"data": {
"id": 4821,
"client_id": 312,
"staff_id": 7,
"start_at": "2026-08-25T09:00:00.000Z",
"end_at": "2026-08-25T09:45:00.000Z",
"status": "scheduled",
"notes": "",
"services": [
{
"service_id": 41,
"staff_id": 7,
"duration_min": 45,
"price": "45.00"
}
]
},
"timestamp": "2026-08-25T09:00:03.512Z"
}
Üç randevu olayının tamamı, saklanan randevudan oluşturulmuş bu tam şekli gönderir: aynı alanlar, aynı türler, start_at/end_at her zaman UTC. Bilinmesi gereken iki detay:
status, olayın gönderildiği anda olan durumdur. Bu yüzden kapora gerektiren bir randevu içinbooking.created,scheduleddeğilprovisionalbildirir, ve birbooking.cancelled, randevunun öncesindeki durum yerinecancelledbildirir.- Bu,
servicesdizisi dahil,GET /api/bookings/:idile aynı alan kümesidir, bu yüzden tek bir ayrıştırıcı her iki kanalı da kapsar.
payment.received:
{
"event": "payment.received",
"data": {
"order_id": 9931,
"order_number": "2026-000482",
"total": 45,
"payment_method": "card",
"client_id": 312
},
"timestamp": "2026-08-25T09:12:47.001Z"
}
client.created / client.updated:
{
"event": "client.created",
"data": {
"id": 312,
"name": "Anna Voorbeeld",
"email": "anna@example.com",
"phone": null,
"mobile": "+31612345678",
"first_name": "Anna",
"last_name": "Voorbeeld",
"preferred_name": null,
"city": "Waalwijk",
"country": "NL",
"created_at": "2026-01-14T10:22:00.000Z"
},
"timestamp": "2026-08-25T09:00:03.512Z"
}
Bunun yukarıdaki GET /api/crm/clients yanıtıyla tam olarak aynı alan kümesi olduğuna dikkat et; toWebhookClient() ve toApiClient() aynı izin listesini paylaşır, bu yüzden bir müşteri bir webhook üzerinden alınsın veya API üzerinden getirilsin aynı görünür.
Bir salon sahibi Webhook'lar ekranından manuel bir test teslimatı gönderdiğinde, "event": "test" ve küçük bir yer tutucu yük içeren bir zarf alırsın. Bu, abone olunabilecek altı olaydan biri değildir; işleyicinin tanımadığı olay adlarını görmezden gelmesini (hata vermemesini) sağla.
İmzaları doğrulama
Her teslimat, webhook'un gizli anahtarı kullanılarak ham istek gövdesi üzerinde HMAC-SHA256 ile imzalanır. Bir API anahtarı gibi, bu gizli anahtar da durağan halde şifrelenir ve tam olarak bir kez gösterilir: oluşturma çağrısının yanıtında, ve salon onu POST /api/developer/webhooks/:id/rotate-secret üzerinden yeniden oluşturursa bir kez daha. Bir webhook üzerinde GET/PUT, gizli anahtarı hiçbir zaman içermez, sadece bir hasSecret boole değeri; dolayısıyla salon onu kaybettiyse, kendileri (veya onların adına sen) onu yeniden oluşturmalı ve senin tarafında imzayı doğrulayan her şeyi güncellemelisin. Onaltılık özet X-Webhook-Signature başlığında gönderilir.
const crypto = require('crypto');
function verify(rawBody, signatureHeader, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}
rawBody, Salonnare'ın hat üzerinden gönderdiği tam baytlar olmalıdır; JSON.stringify(JSON.parse(rawBody)) değil, ve o zamandan beri yeniden serileştirdiğin herhangi bir nesne de değil. Yeniden serileştirme anahtar sırasını ve boşlukları değiştirir, bu da baytları değiştirir, bu da imzayı değiştirir. Bu, en yaygın tek entegrasyon hatasıdır ve nedenine işaret eden net bir hata mesajı olmadan hiçbir zaman eşleşmeyen bir imza üretir. Gövdeyi, framework'ün onu JSON olarak ayrıştırmasından önce ham bir metin/tampon olarak oku ve onu özetle (hash'le).
crypto.timingSafeEqual, iki tampon bayt uzunluğunda farklı olduğunda (false döndürmek yerine) hata fırlatır, bu yüzden eksik veya hatalı biçimli bir X-Webhook-Signature başlığı, kontrolü sadece başarısız kılmak yerine yukarıdaki verify()'ı çökertir. Buna karşı koru; timingSafeEqual'i çağırmadan önce başlığın var olduğunu ve 64 karakterlik bir onaltılık metin olduğunu kontrol et, veya çağrıyı bir try/catch içine al.
Teslimat davranışı
- Tek deneme, yeniden deneme yok. Uç noktan kapalıysa veya hata veriyorsa, Salonnare o teslimatı kuyruğa almaz veya yeniden denemez. Entegrasyonunu, her olayın geleceğini varsaymak yerine ara sıra kaçırılan bir olaya tolerans gösterecek şekilde tasarla (örneğin, periyodik olarak bir
from/topenceresiyleGET /api/bookingsüzerinden mutabakat sağlayarak). - 10 saniyelik zaman aşımı. Uç noktan 10 saniye içinde yanıt vermezse, teslimat başarısız olarak kabul edilir.
- Yönlendirmeler takip edilmez. Bir
3xxyanıtı, bir4xx/5xxile tam olarak aynı şekilde başarısız bir teslimat olarak sayılır. Kaydettiğin tam URL'den2xxyanıt ver. - 10 ardışık başarısızlıktan sonra otomatik devre dışı bırakma. Bir webhook art arda 10 kez başarısız olduğunda (zaman aşımları, 2xx olmayan yanıtlar ve engellenen/geçersiz URL'lerin tümü sayılır), Salonnare onu otomatik olarak devre dışı bırakır. Salon sahibinin Webhook'lar ekranından onu yeniden etkinleştirmesi gerekir; yeniden etkinleştirme başarısızlık sayacını sıfırlar.
- Teslimat günlükleri salonun erişimindedir, uç noktanın değil. Bir salon yöneticisi, uygulamada
GET /api/developer/webhooks/:id/logsüzerinden bir webhook'un son teslimatlarını (durum kodu, yanıt gövdesi, süre) inceleyebilir; bu çağrı bir yönetici girişi gerektirir (bir API anahtarı değil), dolayısıyla bu, salonun kendi entegrasyonunu hata ayıklaması içindir, senin alıcı servisinin çağırdığı bir şey değil.
URL gereksinimleri
Kaydettiğin webhook URL'sinin internet üzerinden herkese açık şekilde erişilebilir olması gerekir. Salonnare bunu hem kaydettiğinde hem de her teslimattan hemen önce yeniden doğrular (bugün güvenli bir şeye çözümlenen ve yarın dahili bir şeye çözümlenen bir alan adına karşı korumak için). Bir URL şu durumlarda veya buna çözümlendiğinde reddedilir; kayıt sırasında bir 400 ile, veya teslimat sırasında başarısız bir deneme olarak:
http:/https:dışında herhangi bir şey (üretimde sadecehttps:)localhost,127.0.0.1,0.0.0.0,::1,::veya köşeli parantezli herhangi bir IPv6 eşdeğeri- Dahili bir ana bilgisayar adı (
server,db,mariadb,mysql,redis,docs,worker) - Gerçek bir özel/loopback/link-local IP adresi (RFC 1918 aralıkları,
169.254.0.0/16,127.0.0.0/8, IPv6 ULAfc00::/7, IPv6 link-localfe80::/10) - Alt bitlerinde bir IPv4 adresini gizleyebilen geçiş gösterimlerinden birindeki bir IPv6 adresi: IPv4 eşlemeli (
::ffff:0:0/96), IPv4 uyumlu (::/96), NAT64 (64:ff9b::/96) ve 6to4 (2002::/16). Bunlar önek üzerinde engellenir, böylece yerleştirilmiş adresin her yazılışı reddedilir;[::ffff:127.0.0.1],[::127.0.0.1]ve[::7f00:1]aynı şekilde. Sıradan global-unicast IPv6'dan etkilenmez - DNS kaydı yukarıdakilerden birine çözümlenen herkese açık bir ana bilgisayar adı; bu, sadece kayıt sırasında bir kez değil, teslimat sırasında kontrol edilir, özellikle sonradan dahili bir adrese yönlendirdiğin bir alan adının da hâlâ engellenmesi için
URL'yi baştan kontrolünde olan bir alan adına yönlendir; bir yer tutucu kaydedip DNS'i daha sonra dahili bir adrese yönlendirmeyi bekleme, ve sadece kayıt sırasındaki kontrole güvenme; teslimat her seferinde yeniden doğrular.