Webhook'lar
Webhook'lar e-posta olaylarını gerçekleştikleri anda sizin sunucunuza POST eder. Örneğin geri dönen bir adresi CRM'inizde işaretleyebilir ya da teslim edilen faturaları kayda geçirebilirsiniz.
Kurulum
Bir uç nokta yazın
Sunucunuzda POST kabul eden ve 2xx dönen, herkese açık bir https:// adresi hazırlayın (yerel ağ ve localhost adresleri güvenlik nedeniyle kabul edilmez; yönlendirmeler izlenmez). İşlemi kuyruğa alıp hemen 200 dönmek en iyisidir; 15 saniyede yanıt gelmezse istek başarısız sayılır.
Panelden ekleyin
İmzayı doğrulayın
Herkes adresinize istek atabilir. Aşağıdaki doğrulama, isteğin gerçekten SingleMail'den geldiğini ve yolda değiştirilmediğini garanti eder.
İstek gövdesi
webhook-id: msg_8c0b6f3e-…
webhook-timestamp: 1791651120
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"type": "email.bounced",
"created_at": "2026-10-10T14:12:00.000Z",
"data": {
"email_id": "abe55c23-533d-41ad-8971-2dad84ad985b",
"from": "Optik Dünyası <fatura@firma.com>",
"to": ["olmayan.adres@gmail.com"],
"subject": "e-Faturanız hazır",
"tags": [{ "name": "type", "value": "invoice" }],
"bounce": { "type": "Permanent", "subType": "General" }
}
}Doğrulama
İmza, webhook-id.webhook-timestamp.gövde metninin, imza anahtarıyla (whsec_ öneki çıkarılıp base64 çözülmüş hali) alınmış HMAC-SHA256 değeridir (Standard Webhooks). Ham gövdeyi kullanın; JSON'u parse edip tekrar yazarsanız imza tutmaz.
import express from "express";
import { SingleMail } from "@singlemail/node";
const sm = new SingleMail();
app.post("/hooks/singlemail", express.text({ type: "*/*" }), (req, res) => {
let event;
try {
event = sm.webhooks.verify({
payload: req.body,
headers: {
id: req.header("webhook-id"),
timestamp: req.header("webhook-timestamp"),
signature: req.header("webhook-signature"),
},
secret: process.env.SINGLEMAIL_WEBHOOK_SECRET,
});
} catch {
return res.sendStatus(400);
}
if (event.type === "email.bounced") markInvalid(event.data.to[0]);
res.sendStatus(200);
});$secret = base64_decode(substr(getenv("SINGLEMAIL_WEBHOOK_SECRET"), 6));
$payload = file_get_contents("php://input");
$signed = $_SERVER["HTTP_WEBHOOK_ID"] . "." . $_SERVER["HTTP_WEBHOOK_TIMESTAMP"] . "." . $payload;
$expected = "v1," . base64_encode(hash_hmac("sha256", $signed, $secret, true));
if (!hash_equals($expected, $_SERVER["HTTP_WEBHOOK_SIGNATURE"])) { http_response_code(400); exit; }
$event = json_decode($payload, true);Olay türleri
| Olay | Ne zaman |
|---|---|
email.sent | Relay e-postayı kabul etti. |
email.delivered | Alıcının sunucusu e-postayı aldı. |
email.delivery_delayed | Teslimat geçici olarak ertelendi. |
email.bounced | Kalıcı olarak geri döndü; adres engellendi. |
email.complained | Alıcı spam olarak işaretledi; adres engellendi. |
email.opened | E-posta açıldı. |
email.clicked | Bir linke tıklandı; payload'da link var. |
email.failed | Gönderilemedi; payload'da neden var. |
email.scheduled | E-posta ileri bir tarihe zamanlandı. |
email.canceled | Zamanlanmış e-posta iptal edildi. |
Tekrar denemeler
2xx dönmeyen ya da zaman aşımına uğrayan istekler artan aralıklarla (5 sn, 10 sn, 20 sn … ~10 dk) toplam 8 kez denenir. Aynı olay birden fazla gelebilir; webhook-id başlığına göre tekilleştirin.
