İçeriğe geç

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).

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.

  1. Uç ekle’yi seçin.
  2. Sunucunuzun adresini girin. Kullanıcı adı ya da parola içermeyen, herkese açık bir https adresi olmalıdır; özel ya da yerel ağların adresleri reddedilir (webhook_url_invalid, webhook_url_not_allowed).
  3. 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.
  4. 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.

OlayNe zaman
member.invitedBiri davet edilir
member.joinedBiri daveti kabul eder, şirket girişiyle katılır ya da SCIM ile eklenir
member.updatedBir üyenin rolü, takımı, saat dilimi, takibi ya da görebildiği takımlar değişir
member.deactivatedBir üye askıya alınır ya da kaldırılır
member.reactivatedAskıdaki bir üye yeniden etkinleştirilir
team.created, team.updated, team.archivedBir takım değişir
time_entry.created, time_entry.updated, time_entry.deleted, time_entry.approved, time_entry.rejectedOffline 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.deletedTek tek vardiyalar (Premium)
project.created, project.updated, project.archived, project.deletedProjeler
task.created, task.updated, task.archived, task.deletedGörevler
time_allocation.created, time_allocation.updated, time_allocation.deletedProjelere ve görevlere atanan süre
invoice.issued, invoice.paid, invoice.voidedMüşteri faturaları (Premium)
webhook.testTest olayı gönder

Bir uç yalnız paketinizdeki özelliklerin olaylarını seçebilir. Paket değişirse artık olmayan özelliklerin olayları gönderilmez.

POST /your/path HTTP/1.1
content-type: application/json
user-agent: Workify360-Webhooks/1.0
webhook-id: 0192f3c4-7a1e-7c55-9b1a-2f6a0e4c1d22
webhook-timestamp: 1759395322
webhook-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.

  • İmzayı JSON’u ayrıştırmadan önce ham gövde üzerinde doğrulayın.
  • webhook-timestamp değeri saatinizden 5 dakikadan fazla uzak olan isteği reddedin.
  • Daha önce işlediği isteği tanımak için webhook-id kullanın: aynı olay birden fazla kez gelebilir.
  • 10 saniye içinde 2xx durumuyla 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.

İ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 base64
import hashlib
import hmac
import 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 False

Herhangi 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 Gone yanı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).

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ı 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.

KodNe yapmalı
webhook_url_invalidKullanıcı adı ya da parola içermeyen eksiksiz bir https adresi girin
webhook_url_not_allowedAdres özel ya da yerel bir ağa ait ya da çözümlenemiyor; herkese açık bir adres kullanın
webhook_event_unknownOlayları listeden seçin; bazı olaylar Premium paketinin bir özelliğini gerektirir
webhook_limit_reachedOrganizasyonunuzun 10 ucu var; artık kullanmadığınız birini silin
webhook_endpoint_not_foundUç silinmiş; sayfayı yenileyin
webhook_delivery_not_foundTeslim 30 günden eski ya da ucuyla birlikte silinmiş
connection_pausedTest olayı göndermeden önce kapatılmış ucu açın