Webhooks
Получайте события доставки в реальном времени: delivered, bounced, complained, opened, clicked.
Настройка
В dashboard укажите URL, на который мы будем слать POST-запросы с событиями. При первой конфигурации автоматически генерируется секрет для HMAC-подписи — сохраните его, он показывается только один раз.
Формат события
{
"id": "ev_a1b2c3...",
"type": "email.delivered",
"created_at": "2026-04-24T10:15:32Z",
"data": {
"email_id": "em_abc123...",
"message_id": "<abc123@yourdomain.ru>",
"to": ["user@example.com"],
"tags": ["orders"],
"ts": "2026-04-24T10:15:32Z"
}
}Типы событий
email.sent— передано SMTP-серверу получателяemail.delivered— SMTP-сервер получателя подтвердил приёмemail.bounced— жёсткий отказ: ящика не существует. Адрес попадает в стоп-листemail.rejected— почтовая служба получателя отклонила письмо по своей политике или репутации отправителя. Ящик существует, адрес НЕ попадает в стоп-лист, удалять его из своей базы не нужноemail.complained— получатель нажал «Это спам»email.opened— письмо открытоemail.clicked— клик по ссылкеemail.failed— отправка не удалась (домен не подтверждён, все адреса в стоп-листе и т.п.)
Верификация HMAC
Каждый запрос содержит заголовки X-Synapsea-Timestamp (unix-секунды) и X-Synapsea-Signature: v1=<hex> — HMAC-SHA256 от строки `${timestamp}.${body}` с вашим секретом. Всегда проверяйте подпись — это защита от подделки событий, а timestamp — от replay-атак.
Node.js (Express)
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/webhooks/mail", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-synapsea-signature"];
const timestamp = req.headers["x-synapsea-timestamp"];
// Replay protection: reject events older than 5 minutes
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.status(401).send("timestamp too old");
}
const expected = "v1=" + crypto
.createHmac("sha256", process.env.SYNAPSEA_WEBHOOK_SECRET)
.update(timestamp + "." + req.body)
.digest("hex");
if (signature !== expected) return res.status(401).send("invalid signature");
const event = JSON.parse(req.body.toString());
console.log(event.type, event.data.email_id);
res.json({ received: true });
});Python (Flask)
import hmac, hashlib, os, time
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/webhooks/mail")
def webhook():
signature = request.headers.get("X-Synapsea-Signature", "")
timestamp = request.headers.get("X-Synapsea-Timestamp", "0")
if abs(time.time() - int(timestamp)) > 300:
abort(401)
secret = os.environ["SYNAPSEA_WEBHOOK_SECRET"].encode()
signed = f"{timestamp}.".encode() + request.data
expected = "v1=" + hmac.new(secret, signed, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
abort(401)
event = request.json
print(event["type"], event["data"]["email_id"])
return {"received": True}Retry-политика
Если ваш endpoint вернул HTTP 2xx — событие считается доставленным. Если 4xx/5xx или таймаут (15 сек) — мы делаем retry до 8 раз с exponential backoff, начиная с 15 секунд (15с, 30с, 1 мин, 2 мин, … до ~30 мин). После исчерпания попыток событие остаётся в статусе failed — историю видно в dashboard → Webhooks.
Тестирование
Для локальной разработки используйте ngrok или webhook.site. История запросов доступна в dashboard → Webhooks.