Webhook'lar
Uygulama başına bir URL kaydedin; worker size batch'ler halinde, o ucun kendi sırrıyla imzalı olaylar POST eder. Uçları Panel › Webhook'lar'da yönetin.
Olaylar
notification.sent · delivered · opened · clicked · dismissed · failed · skipped
subscription.invalidated
campaign.completed
notification.* analitik olaylarına bire bir eşlenir. subscription.invalidated bir transport ölü token bildirdiğinde atılır. campaign.completed fan-out bitince atılır — her push kuyruktadır, mutlaka gönderilmiş değil.
Toplulaştırma
Fan-out hızında olay başına POST, sunucunuzda saniyede binlerce istek demek. Hem API (izleme pingleri) hem worker (gönderim sonuçları) olayları uç başına en fazla 1 saniye ya da 100 olay boyunca (hangisi önce) tamponlar, sonra bir teslimat işi kuyruğa atar. Worker batch başına bir POST yapar.
Yük
POST https://hooks.acme.example/opennotification
content-type: application/json
x-opennotification-signature: t=1758000000,v1=5f1a…
{
"id": "dlv_3kJ9…",
"events": [
{
"id": "evt_8sQ2…",
"type": "notification.delivered",
"ts": "2026-09-17T12:34:56.789Z",
"appId": "66f1…",
"campaignId": "66f2…",
"userId": "66f3…",
"externalId": "user_123",
"subscriptionId": "66f4…",
"platform": "ios"
},
{
"id": "evt_9tR3…",
"type": "notification.failed",
"ts": "…", "appId": "…", "campaignId": "…", "userId": "…", "subscriptionId": "…", "platform": "android",
"data": { "code": "UNREGISTERED" }
},
{
"id": "evt_0uS4…",
"type": "campaign.completed",
"ts": "…", "appId": "…", "campaignId": "…",
"data": { "queued": 1650, "byPlatform": { "ios": 700, "android": 690, "web": 260 }, "name": "Hafta sonu indirimi", "transactional": false }
}
]
}
data başarısızlık kodu ya da kampanya istatistiği taşır — asla token ya da endpoint değil. Kullanıcının external id'si varsa externalId bulunur.
İmzayı doğrulama
Başlık t=<unix saniye>,v1=<hex HMAC-SHA256(secret, "<t>.<ham gövde>")>. JSON ayrıştırmadan önce ham istek gövdesiyle doğrulayın ve 5 dakikadan eski zaman damgalarını reddedin.
// Bun / Node
import { verifyWebhookSignature } from "@opennotification/core";
app.post("/opennotification", async (req) => {
const body = await req.text();
const check = verifyWebhookSignature(
process.env.WHSEC!,
body,
req.headers.get("x-opennotification-signature") ?? "",
{ toleranceSeconds: 300 },
);
if (!check.ok) return new Response(check.reason, { status: 401 }); // malformed | bad_signature | too_old
const { events } = JSON.parse(body);
// …
return new Response(null, { status: 204 });
});
Paket olmadan:
import hmac, hashlib, time
def verify(secret: str, header: str, body: bytes, tolerance=300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t, v1 = parts["t"], parts["v1"]
if abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
Yanıtlar ve tekrarlar
| Yanıtınız | Worker |
|---|---|
2xx | Bitti. |
5xx, 429, 408, ağ hatası, timeout | 30 sn'den başlayan üstel backoff ile 5 deneme. |
diğer 4xx | Sizin hatanız; tekrar denenmez. |
Hızlı yanıtlayın (204 deyip asenkron işleyin). Batch'ler en az bir kez teslimdir: event.id'yi kendi tarafınızda idempotent tutun.
Otomatik kapanma
Ardışık 50 batch denemelerini tükettikten sonra uç kapatılır (satırda disabledReason, panelde otomatik kapandı); ölü bir URL sonsuza dek deneme yakmasın. Alıcıyı düzeltip Aç'a basın; sayaç sıfırlanır.
Kısıtlar
- Yalnızca HTTPS URL.
localhost,127.*ve169.254.169.254reddedilir (SSRF koruması). - Sırlar (
whsec_…) bir kez gösterilir, mühürlü saklanır ve panelden döndürülebilir — eskisi anında çalışmaz olur. - Panelden test: tek
{ "type": "ping" }olaylı imzalı batch, eşzamanlı gönderilir; yanıt durumu gösterilir.