Webhook'lar
Webhook, Focus’ta bir şey değiştiğinde, örneğin bir zaman kaydı onaylandığında ya da bir üye katıldığında, sunucunuza bir HTTPS isteği gönderir. Webhook’lar genel API ve API anahtarları gibi Premium paketine dahildir (API anahtarları ve genel API).
Uç ekleme
Section titled “Uç ekleme”Hesap sahipleri ve şirket yöneticileri webhook’ları Ayarlar → Geliştirici sayfasında yönetir. webhooks:manage
kapsamlı bir API anahtarı olan bir betik uçları listeleyebilir, ekleyebilir, değiştirebilir, silebilir ve teslimatlarını
okuyabilir (API referansı); deneme olayı göndermek, sırrı yenilemek ve bir teslimatı yeniden
denemek panelde kalır.
- Uç ekle’yi seçin.
- Sunucunuzun adresini girin. Kullanıcı adı ya da parola içermeyen, herkese açık bir
httpsadresi olmalıdır; özel ya da yerel ağların adresleri reddedilir (webhook_url_invalid,webhook_url_not_allowed). - Alınacak olayları (var olmayan ya da paketinizde olmayan bir olay için
webhook_event_unknown) ve isteğe bağlı bir açıklamayı seçin. - Ucun
whsec_ile başlayan imzalama sırrı bir kez gösterilir. Onu sunucunuzun yapılandırmasına kopyalayın.
Bir organizasyonun en fazla 10 ucu olabilir (webhook_limit_reached). Test olayı gönder sunucunuzu denemek için
bir webhook.test olayı gönderir.
Olaylar
Section titled “Olaylar”| Olay | Ne zaman |
|---|---|
member.invited | Biri davet edilir |
member.joined | Biri daveti kabul eder, şirket girişiyle katılır ya da SCIM ile eklenir |
member.updated | Bir üyenin rolü, takımı, saat dilimi, takibi ya da görebildiği takımlar değişir |
member.deactivated | Bir üye askıya alınır ya da kaldırılır |
member.reactivated | Askıdaki bir üye yeniden etkinleştirilir |
team.created, team.updated, team.archived | Bir takım değişir |
time_entry.created, time_entry.updated, time_entry.deleted, time_entry.approved, time_entry.rejected | Offline zaman ve diğer zaman kayıtları değişir ya da karara bağlanır |
absence.requested, absence.updated, absence.approved, absence.rejected, absence.canceled | İzin talepleri (Premium) |
shift.created, shift.updated, shift.deleted | Tek tek vardiyalar (Premium) |
project.created, project.updated, project.archived, project.deleted | Projeler |
task.created, task.updated, task.archived, task.deleted | Görevler |
time_allocation.created, time_allocation.updated, time_allocation.deleted | Projelere ve görevlere atanan süre |
invoice.issued, invoice.paid, invoice.voided | Müşteri faturaları (Premium) |
webhook.test | Test olayı gönder |
Bir uç yalnız paketinizdeki özelliklerin olaylarını seçebilir. Paket değişirse artık olmayan özelliklerin olayları gönderilmez.
Bir istek nasıl görünür
Section titled “Bir istek nasıl görünür”POST /your/path HTTP/1.1content-type: application/jsonuser-agent: Workify360-Webhooks/1.0webhook-id: 0192f3c4-7a1e-7c55-9b1a-2f6a0e4c1d22webhook-timestamp: 1759395322webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{ "type": "time_entry.approved", "timestamp": "2026-10-02T09:15:22.123Z", "data": { "object": "time_entry", "id": "0192f3c4-…", "tenantId": "0191aa…", "subjectUserId": "0191bb…", "actor": { "type": "user", "id": "0191cc…" }, "changes": ["status"], "url": "https://api.focus.workify360.com/v1/time-entries/0192f3c4-…" }}Gövde bilerek incedir: kimlikleri, olay türünü, değişen alanların adlarını ve genel API nesneyi okuyabiliyorsa onun
url’sini taşır. Adları, e-posta adreslerini, başlıkları, açıklamaları ya da notları hiçbir zaman taşımaz. Ayrıntı
için sunucunuz url’yi bir API anahtarıyla okur; böylece yalnız o anahtarın sahibinin görebildiğini görür. Silinmiş
nesnelerin url’si yoktur.
Sunucunuzun yapması gerekenler
Section titled “Sunucunuzun yapması gerekenler”- İmzayı JSON’u ayrıştırmadan önce ham gövde üzerinde doğrulayın.
webhook-timestampdeğeri saatinizden 5 dakikadan fazla uzak olan isteği reddedin.- Daha önce işlediği isteği tanımak için
webhook-idkullanın: aynı olay birden fazla kez gelebilir. - 10 saniye içinde
2xxdurumuyla yanıt verin ve yavaş işi sonra, örneğin bir kuyrukta yapın. Yönlendirmeler izlenmez ve başarısızlık sayılır.
İmzayı doğrulama
Section titled “İmzayı doğrulama”İmza Standard Webhooks belirtimini izler: webhook-id, webhook-timestamp ve ham gövdenin noktalarla birleştirilmiş
hâlinin, sırrınızın whsec_’ten sonraki base64 kısmıyla anahtarlanmış HMAC-SHA256’sı, base64 olarak.
webhook-signature başlığı boşlukla ayrılmış birden fazla imza taşıyabilir; biri eşleşirse istek gerçektir. Sabit
zamanda karşılaştırın, hiçbir zaman == ile değil; böylece karşılaştırma beklenen değeri sızdırmaz.
Node.js: Express’te rota ham gövdeyi vermelidir.
import { createHmac, timingSafeEqual } from 'node:crypto';
const TOLERANCE_SECONDS = 5 * 60;
export function verifyWorkify360Webhook(secret, headers, rawBody) { const id = headers['webhook-id']; const timestamp = headers['webhook-timestamp']; const signatures = headers['webhook-signature']; if (!id || !timestamp || !signatures) return false; const seconds = Number(timestamp); if (!Number.isInteger(seconds) || Math.abs(Date.now() / 1000 - seconds) > TOLERANCE_SECONDS) return false; const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64'); const expected = createHmac('sha256', key).update(`${id}.${timestamp}.`).update(rawBody).digest(); return signatures.split(' ').some((entry) => { const [version, signature] = entry.split(','); if (version !== 'v1' || !signature) return false; const given = Buffer.from(signature, 'base64'); return given.length === expected.length && timingSafeEqual(given, expected); });}
// app.post('/workify360', express.raw({ type: 'application/json' }), (req, res) => {// if (!verifyWorkify360Webhook(process.env.WORKIFY360_WEBHOOK_SECRET, req.headers, req.body)) return res.sendStatus(400);// queue.add(JSON.parse(req.body)); // handle it later// res.sendStatus(204);// });Python:
import base64import hashlibimport hmacimport time
TOLERANCE_SECONDS = 5 * 60
def verify_workify360_webhook(secret: str, headers, raw_body: bytes) -> bool: msg_id = headers.get("webhook-id") timestamp = headers.get("webhook-timestamp") signatures = headers.get("webhook-signature") if not msg_id or not timestamp or not signatures: return False try: seconds = int(timestamp) except ValueError: return False if abs(time.time() - seconds) > TOLERANCE_SECONDS: return False key = base64.b64decode(secret.removeprefix("whsec_")) signed = f"{msg_id}.{timestamp}.".encode() + raw_body expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()) for entry in signatures.split(" "): version, _, signature = entry.partition(",") if version == "v1" and hmac.compare_digest(signature.encode(), expected): return True return FalseHerhangi bir Standard Webhooks kütüphanesi de aynı imzayı doğrular.
Yeniden denemeler ve kendiliğinden kapanma
Section titled “Yeniden denemeler ve kendiliğinden kapanma”2xx yanıtı almayan teslim yeniden denenir. İlk deneme dahil Focus en fazla 8 kez dener: hemen, sonra 5 saniye,
5 dakika, 30 dakika, 2 saat, 5 saat, 10 saat ve 10 saat sonra; toplam yaklaşık 27,5 saat. Her yeniden deneme tam
olarak aynı gövdeyi gönderir.
410 Goneyanıtı ucu hemen kapatır.- 5 gündür hiç başarılı teslimi olmayan bir uçta bir teslim de denemelerini tüketince uç kapatılır.
- Bir uç kapatılınca hesap sahipleri ve şirket yöneticileri e-postayla da bildirim alır. Ucu yeniden açmak hata sayısını sıfırlar.
Ucun sayfası son 30 günün teslimlerini durumu, deneme sayısı, son durum kodu, hata ve süresiyle listeler; sunucunuzun
yanıtları saklanmaz. Başarısız bir teslim oradan yeniden gönderilebilir. Kapatılmış bir uca test olayı gönderilemez
(connection_paused).
Ucun sahibi
Section titled “Ucun sahibi”Bir uç onu oluşturan hesap sahibine ya da şirket yöneticisine aittir. Bu kişi artık hesap sahibi ya da şirket yöneticisi değilse ya da ayrılırsa uç duraklar ve hiçbir şey teslim edilmez; diğer şirket yöneticilerine bildirim gider. Başka bir hesap sahibi ya da şirket yöneticisi ucu devralarak sürdürür ve bekleyen teslimler o zaman gönderilir.
Sırrı yenileme
Section titled “Sırrı yenileme”Sırrı yenile yeni bir sır oluşturur ve bir kez gösterir. Sonraki 24 saat boyunca her istek biri yeni, biri eski sırla olmak üzere iki imza taşır; böylece sunucunuzdaki sırrı hiçbir isteği kaçırmadan değiştirebilirsiniz. 24 saatten sonra yalnız yeni sır imzalar.
Hata mesajları
Section titled “Hata mesajları”| Kod | Ne yapmalı |
|---|---|
webhook_url_invalid | Kullanıcı adı ya da parola içermeyen eksiksiz bir https adresi girin |
webhook_url_not_allowed | Adres özel ya da yerel bir ağa ait ya da çözümlenemiyor; herkese açık bir adres kullanın |
webhook_event_unknown | Olayları listeden seçin; bazı olaylar Premium paketinin bir özelliğini gerektirir |
webhook_limit_reached | Organizasyonunuzun 10 ucu var; artık kullanmadığınız birini silin |
webhook_endpoint_not_found | Uç silinmiş; sayfayı yenileyin |
webhook_delivery_not_found | Teslim 30 günden eski ya da ucuyla birlikte silinmiş |
connection_paused | Test olayı göndermeden önce kapatılmış ucu açın |