Giden webhook’lar

Webhook kataloğu

Çalışma alanında bir şey olduğunda SoliAgile senin adresine imzalı bir POST gönderir. Bu sayfa hangi olayların gönderildiğini, yükün nasıl göründüğünü ve imzayı nasıl doğrulayacağını anlatıyor.

Abone olunabilir olaylar

Uç nokta oluştururken bu olaylardan istediklerini seçersin; hiç seçmezsen hepsi gönderilir. Ayarlar → Webhook’lar’dan yönetilir.

OlayNe zaman gönderilir
issue.createdYeni bir issue açıldığında.
issue.updatedIssue’nun alanları değiştiğinde (başlık, açıklama, öncelik, tahmin, proje, döngü…).
issue.deletedIssue kalıcı olarak silindiğinde. Yük, silinme anındaki son hâli taşır.
issue.assignedAtanan kişi değiştiğinde — atama kaldırıldığında da gönderilir.
issue.status_changedIssue başka bir iş akışı durumuna geçtiğinde.
comment.createdBir issue’ya yorum yazıldığında.
comment.deletedYorum silindiğinde.

Teslimat geçmişinde webhook.test tipini de görebilirsin: “Test gönder” düğmesinin ürettiği teslimat. Abone olunabilir bir olay değildir; uç noktanın erişilebilirliğini ve imza doğrulamanı kanıtlamak için vardır.

Yük şekli

Her teslimat aynı zarfla gelir; olaya özgü kısım data alanındadır.

{
  "id": "01JZ...",                       // webhook-id ile AYNI — dedup anahtarı
  "type": "issue.status_changed",
  "occurredAt": "2026-07-31T09:14:22.031Z",
  "tenant": { "id": "…", "slug": "acme" },
  "actor": { "id": "…", "displayName": "Ada Lovelace" },
  "data": { "issue": { "identifier": "ACM-142", "title": "…", "stateId": "…" } }
}
  • webhook-id başlığı zarfın id alanıyla aynıdır — tekrarları bununla ayıkla.
  • Sıra garantisi yoktur: iki olay farklı sırada ulaşabilir. Olayın gerçekleştiği an occurredAt alanındadır.
  • data olay anının fotoğrafıdır; kaynak kayıt sonradan değişse bile güncellenmez.
  • Başarısız teslimatlar artan aralıklarla yeniden denenir. 2xx dışındaki her yanıt başarısız sayılır, bu yüzden uç noktan işi kuyruğa alıp hemen 2xx dönmeli.

İmza doğrulama

Her istek üç başlık taşır. İmza, Standard Webhooks (svix) konvansiyonunu izler.

webhook-idTeslimatın benzersiz kimliği — tekrarları ayıklamak için de kullanılır.
webhook-timestampGönderim anı (Unix saniye). Çok eski istekleri reddet: tekrar saldırılarına karşı ilk savunma.
webhook-signatureBoşlukla ayrılmış bir veya daha çok `v1,<base64>` adayı. Sır döndürüldüğünde geçiş süresince iki aday birlikte gelir.

SDK ile (önerilen)

@vennyx/soliagile-sdk doğrulamayı hazır getirir: birden çok imza adayını, sabit zamanlı karşılaştırmayı ve zaman kayması kontrolünü kendisi yapar.

import { verifyWebhookSignature } from '@vennyx/soliagile-sdk';

const result = await verifyWebhookSignature({
  secret: process.env.SOLIAGILE_WEBHOOK_SECRET,
  headers: request.headers,
  body: rawBody,               // HAM gövde — JSON.parse ETMEDEN
});

if (!result.valid) {
  // 'missing-headers' | 'malformed-secret' | 'stale-timestamp' | 'signature-mismatch'
  return new Response(result.reason, { status: result.reason === 'missing-headers' ? 400 : 401 });
}

SDK olmadan

Başka bir dilde yazıyorsan hesabın tamamı şu kadar:

import { createHmac, timingSafeEqual } from 'node:crypto';

// Sır: "whsec_<base64>". Önek YALNIZ insan için — HMAC anahtarına GİRMEZ.
const key = Buffer.from(secret.slice('whsec_'.length), 'base64');

const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signed = `${id}.${timestamp}.${rawBody}`;   // rawBody: HAM gövde (JSON.parse ETMEDEN)

const expected = 'v1,' + createHmac('sha256', key).update(signed).digest('base64');

// Header boşlukla ayrılmış BİRDEN ÇOK aday taşıyabilir (sır rotasyonu).
const ok = headers['webhook-signature']
  .split(' ')
  .some((candidate) => candidate.length === expected.length && timingSafeEqual(Buffer.from(candidate), Buffer.from(expected)));

İmzayı HAM gövdeyle hesapla. JSON’u parse edip yeniden serileştirirsen anahtar sırası veya boşluklar değişir ve imza tutmaz — bu en sık yapılan hata.

Sonrası

Uç noktaları API’den de yönetebilirsin; SDK imza doğrulamasını hazır getirir.