Pushizo Docs

Webhook'lar

Pushizo, app'inizdeki olayları (teslimat, açılma, abonelik, kullanıcı, olay, dönüşüm, kampanya) HTTPS üzerinden sizin sunucunuza imzalı olarak iletir. Endpoint'ler dashboard'da Geliştirici → Webhooks ekranından ya da…

Pushizo, app'inizdeki olayları (teslimat, açılma, abonelik, kullanıcı, olay, dönüşüm, kampanya) HTTPS üzerinden sizin sunucunuza imzalı olarak iletir. Endpoint'ler dashboard'da Geliştirici → Webhooks ekranından ya da Public API'den (/v1/apps/{app_id}/webhooks, admin kapsamı) yönetilir.

Gövdeyi Liquid şablonuyla dönüştürmek, olayları koşullarla (ör. yalnızca belirli kampanya) daraltmak ve sonucu önizlemek için bkz. Event Streams. delivery.* olayları data.campaign_id alanını da taşır (kampanya dışı mesajlarda null).

Olay türleri

GrupOlaylar
Mesajmessage.queued, message.completed, message.failed
Teslimatdelivery.sent, delivery.delivered, delivery.failed, delivery.opened, delivery.clicked, delivery.bounced, delivery.complained, delivery.unsubscribed
In-appin_app.impression, in_app.clicked (button_id), in_app.dismissed
Aboneliksubscription.created, subscription.unsubscribed, subscription.invalidated
Kullanıcıuser.created, user.updated, user.merged, user.deleted
Olayevent.received (olay adı filtresi zorunlu: event_filters.event_names, en az bir ad)
Dönüşümconversion.created
Kampanyacampaign.scheduled, campaign.started, campaign.completed
Journeyjourney.entered, journey.step_completed, journey.completed, journey.exited

E-posta/SMS geri bildirimleri (Faz 10, bkz. messaging.md) aynı olaylarla iletilir: sağlayıcı teslimi delivery.delivered, açılma pikseli/tıklama yönlendirmesi ve sağlayıcı açılma/tıklama olayları delivery.opened / delivery.clicked (url alanıyla), kalıcı bounce delivery.failed (error_code: "bounced", geriye dönük uyum) ve delivery.bounced (bounce_type: "hard", error_code, error_message) + subscription.invalidated; şikâyet delivery.complained, abonelikten çıkma bağlantısı delivery.unsubscribed (her ikisi teslimat başına bir kez) + subscription.unsubscribed; SMS STOP subscription.unsubscribed.

In-app olayları SDK'nın gösterim/tıklama/kapatma bildiriminden üretilir: data = { in_app_message_id, name, subscription_id, user: { id, external_id }, button_id? }. delivery.* ve in_app.* olayları batch modunda gruplanabilir.

Journey olayları (Faz 11, bkz. journeys.md) şu ortak data alanlarını taşır: { journey_id, run_id, version, user: { id, external_id } }. Olaya göre ek alanlar:

  • journey.entered: entry (giriş bağlamı).
  • journey.step_completed: node_id, node_type, status (completed/skipped/failed), branch, result. Batch modunda gruplanabilir.
  • journey.completed ve journey.exited: reason (ör. manual, journey_archived, çıkış kuralı), status, goal_reached, goal_id.

Toplu içe aktarma (CSV import) ile oluşan kullanıcı/abonelikler webhook üretmez.

Gövde

{
  "id": "whe_01J…",
  "type": "delivery.clicked",
  "created_at": "2026-10-07T12:00:00.000Z",
  "app_id": "app_01J…",
  "data": {
    "delivery_id": "dlv_…",
    "message_id": "msg_…",
    "subscription_id": "sub_…",
    "channel": "ios_push",
    "user": { "id": "usr_…", "external_id": "u1" },
    "button_id": "track"
  }
}

Tek olay gövdesi 64 KB'ı aşarsa data yerine { "truncated": true, "reason": "payload_too_large", "subject_id": … } gönderilir; ayrıntıyı API'den okuyun.

Batch modu (endpoint ayarı batch: true): yüksek hacimli delivery.* olayları 100'erli tek istekte { "events": [ … ] } gövdesiyle gönderilir; diğer olaylar tekil kalır.

Başlıklar

BaşlıkAçıklama
Pushizo-Signaturet=<unix saniye>,v1=<hex>; rotasyon penceresinde iki v1
Pushizo-Event-IdOlay kimliği (whe_…); aynı olay tekrar gönderilirse (retry/replay) değişmez — tekrarları bununla ayıklayın
Pushizo-Event-TypeOlay türü; batch gövdede batch
Pushizo-Batch-IdYalnızca batch gövdede
Pushizo-Delivery-AttemptDeneme sayısı (1–7)

Endpoint'e eklediğiniz özel başlıklar (ör. Authorization) da gönderilir; değerleri şifreli saklanır ve API'de yalnızca adları görünür.

İmza doğrulama

v1 = HMAC-SHA256(secret, t + "." + ham_gövde) (hex). Alıcı:

  1. Ham gövdeyi kullanmalıdır (JSON'u ayrıştırıp yeniden serileştirmeyin).
  2. t değerinin şimdiden en fazla 5 dakika farklı olduğunu doğrulamalıdır — eski t ile tekrar gönderilen (replay) istekler reddedilir.
  3. İmzayı sabit zamanlı karşılaştırmayla doğrulamalıdır; başlıktaki v1 imzalarından biri eşleşirse geçerlidir.
  4. Pushizo-Event-Id ile tekrarları ayıklamalı ve hızlıca 2xx dönmelidir (ağır işi kuyruğa alın; zaman aşımı 10 sn).

Node.js (@pushizo/node)

import express from 'express';
import { constructWebhookEvents, WebhookSignatureError } from '@pushizo/node';

const app = express();
app.post('/hooks/pushizo', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    const events = constructWebhookEvents({
      payload: req.body, // Buffer (ham gövde)
      header: req.headers['pushizo-signature'],
      secret: process.env.HS_WEBHOOK_SECRET!,
    });
    for (const event of events) enqueue(event); // Pushizo-Event-Id / event.id ile tekrarları ayıklayın
    res.sendStatus(204);
  } catch (err) {
    if (err instanceof WebhookSignatureError) return res.status(400).send(err.reason);
    throw err;
  }
});

SDK kullanmadan:

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

function verify(rawBody: Buffer, header: string, secret: string, toleranceSec = 300): boolean {
  const pairs = header.split(',').map((p) => p.split('=') as [string, string]);
  const t = Number(pairs.find(([k]) => k === 't')?.[1]);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest();
  return pairs
    .filter(([k]) => k === 'v1')
    .some(([, v]) => {
      const sig = Buffer.from(v, 'hex');
      return sig.length === expected.length && timingSafeEqual(sig, expected);
    });
}

PHP (pushizo/pushizo-php)

$payload = file_get_contents('php://input');            // ham gövde
$header  = $_SERVER['HTTP_PUSHIZO_SIGNATURE'] ?? null;
try {
    $events = Pushizo\Webhook::constructEvents($payload, $header, getenv('HS_WEBHOOK_SECRET'));
} catch (Pushizo\WebhookSignatureException $e) {
    http_response_code(400);
    exit($e->reason);
}
foreach ($events as $event) {
    // $event['id'] ile tekrarları ayıklayın
}
http_response_code(204);

SDK kullanmadan: hash_hmac('sha256', $t . '.' . $payload, $secret) ve hash_equals(); abs(time() - $t) <= 300.

Doğrulama algoritması @pushizo/node (sdk/node/src/__tests__), PHP SDK (sdk/php/tests/run.php) ve sunucu (packages/webhooks) testlerinde aynı vektörlerle sınanır; eski t ile replay reddi testlerdedir.

Secret yönetimi

  • Secret (whsec_…) yalnızca endpoint oluşturulurken ve rotasyonda bir kez gösterilir; dashboard'da "Göster" ile tekrar görüntülenebilir (audit kaydı oluşur). Sunucuda envelope encryption ile şifreli saklanır.
  • Rotasyon (POST …/webhooks/{id}/rotate-secret): yeni secret hemen kullanılır; eski secret 24 saat boyunca ikinci v1 imzası olarak gönderilmeye devam eder. Alıcınızı bu süre içinde yeni secret'a geçirin.

Yeniden deneme, dead, replay

  • 2xx dışı yanıt, zaman aşımı (10 sn), ağ hatası veya yönlendirme (3xx izlenmez) başarısızlıktır.
  • Bekleme planı: 1 dk, 5 dk, 30 dk, 2 sa, 6 sa, 24 sa. İlk istek + 6 yeniden deneme (toplam 7 istek) başarısız olursa teslim dead olur.
  • Dashboard veya API ile tekil (POST …/webhooks/deliveries/{id}/replay) ya da toplu (POST …/webhooks/{id}/replay, başarısız/ölü, en fazla 1000) yeniden gönderim yapılabilir. Replay aynı Pushizo-Event-Id ile gider.
  • Endpoint sağlığı: art arda başarısızlık serisi 7 günü geçerse endpoint failing olur ve organizasyon sahip/yöneticilerine e-posta gider; 14 günü geçerse disabled olur ve bekleyen teslimler kapatılır. Endpoint'i düzelttikten sonra etkinleştirip ölü teslimleri toplu replay edin. Herhangi bir başarılı teslim seriyi sıfırlar.
  • Adil teslim: bir tarama turunda endpoint başına sınırlı sayıda teslim alınır ve endpoint başına en fazla 8 eşzamanlı istek yapılır. Bir endpoint'e istek başarısız olursa o turdaki kalan teslimleri gönderilmez; deneme sayılmadan yaklaşık 30 sn sonra yeniden denenir. Böylece yavaş veya çökmüş bir alıcı diğer endpoint'leri geciktirmez.
  • Birden fazla worker güvenle paralel çalışır: teslim, talep eden worker'ın kira süresi içinde tamamlanamayacaksa gönderilmez (aynı teslimin iki kez gönderilmesi önlenir).
  • Teslim kayıtları 30 gün saklanır. Test gönder webhook.test olayını anında gönderir ve sonucu gösterir (yeniden denenmez).

Kişisel veri (KVKK/GDPR)

  • Kullanıcı silindiğinde (DELETE /users/by/…) o kullanıcıya ait tüm webhook teslim kayıtları (gönderilmiş veya bekleyen; kullanıcı, alias, tag/özellik, olay ve dönüşüm yükleri) silinir; alıcıya yalnızca user.deleted bildirimi gider.
  • App'in "Verileri temizle" işlemi webhook teslim geçmişini de siler (endpoint yapılandırması korunur).

İzleme

webhook_deliveries_total{status}, webhook_delivery_latency_seconds (olaydan başarılı teslime; SLO p95 < 30 sn) ve webhook_request_duration_seconds{outcome} metrikleri /metrics üzerinden yayımlanır. Yerel ölçüm: node tests/load/webhooks-bench.mjs --key … --app ….

Güvenlik (SSRF)

  • Yalnızca https:// URL'leri kabul edilir; URL içinde kullanıcı adı/parola, localhost/.local/.internal gibi yerel adlar ve özel/ayrılmış IP adresleri reddedilir.
  • Gönderimde ana makine adı DNS ile çözülür; çözülen adreslerden biri bile özel/ayrılmışsa istek yapılmaz ve bağlantı denetlenen adrese sabitlenir (DNS rebinding koruması).
  • Yerel geliştirmede kendi makinenizdeki alıcıyla deneme için .env içinde WEBHOOK_ALLOW_INSECURE_TARGETS=true (http ve yerel adreslere izin) verilebilir; bu ayar NODE_ENV=production iken açılamaz.

Bu sayfada