Инструкция · Данные наружу
Приём подписанных вебхуков
Sendalto может пересылать выбранные события — из Events API или синхронизации HubSpot — на ваш адрес. Каждый запрос подписан, так что вы можете убедиться, что он пришёл от Sendalto.
Сверено с Sendalto
1. Добавьте подписку
- В разделе «Настройки → API и приватность» добавьте вебхук: адрес и точные названия событий для пересылки, например
trial_startedилиcrm.contact.changed(до 50). - Скопируйте секрет подписи. Он показывается один раз — сохраните его на своём сервере.
- Доставляются только события, записанные после создания подписки; история не пересылается.
Адрес должен быть публичным https:// на порту 443 с доменным именем — без IP-адресов, частных сетей, логина в адресе и #фрагмента. Перенаправления не выполняются.
2. Как выглядит доставка
POST /sendalto-webhook HTTP/1.1
content-type: application/json
x-sendalto-signature: t=1791279000,v1=5f0c…e9a1
x-sendalto-delivery: 0b9d7c52-…
{
"id": "6c7e1d3a-…",
"type": "trial_started",
"occurredAt": "2026-10-06T09:30:00.000Z",
"data": {
"email": "[email protected]",
"properties": { "plan": "team", "seats": 5 }
}
}id— ID события,type— его название,occurredAt— время события; вdata— адрес человека и свойства события, поэтому обращайтесь с доставками как с персональными данными.x-sendalto-deliveryобозначает эту доставку и не меняется при повторах.x-sendalto-signatureимеет видt=<секунды unix>,v1=<HMAC в hex>.
3. Проверьте подпись
- Прочитайте сырое тело запроса как байты, до разбора JSON.
- Разделите заголовок на
tиv1. Отклоните запрос, еслиtрасходится с вашими часами больше чем примерно на 5 минут. - Вычислите HMAC-SHA256 секретом подписи от строки
<t>.<сырое тело>и закодируйте в hex. - Сравните результат с
v1за постоянное время. При несовпадении отклоните запрос. - Пропустите доставки, чей
x-sendalto-deliveryвы уже обработали, и ответьте2xx.
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';
const SECRET = process.env.SENDALTO_WEBHOOK_SECRET;
const TOLERANCE_SECONDS = 300;
const app = express();
// Verify against the raw bytes: parsing and re-serialising JSON changes the body.
app.post('/sendalto-webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const header = req.get('x-sendalto-signature') ?? '';
const parts = Object.fromEntries(header.split(',').map(part => part.trim().split('=', 2)));
const timestamp = Number(parts.t);
if (!Number.isInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) {
return res.sendStatus(400); // stale or missing timestamp
}
const expected = createHmac('sha256', SECRET).update(`${parts.t}.`).update(req.body).digest();
const received = Buffer.from(parts.v1 ?? '', 'hex');
if (received.length !== expected.length || !timingSafeEqual(received, expected)) {
return res.sendStatus(401); // not signed with your secret
}
// Delivery is at least once: remember each x-sendalto-delivery and skip repeats.
const deliveryId = req.get('x-sendalto-delivery');
if (await alreadyProcessed(deliveryId)) return res.sendStatus(200);
const event = JSON.parse(req.body.toString('utf8'));
await handleEvent(event); // event.id, event.type, event.occurredAt, event.data.email, event.data.properties
await markProcessed(deliveryId);
res.sendStatus(200);
});4. Повторы
- Доставленной считается любая
2xxза 10 секунд. Отвечайте быстро, а долгую работу делайте после ответа. - Тайм-ауты, сетевые ошибки,
408,425,429и5xxповторяются с растущими паузами — примерно 1, 2, 4, 8, 16, 32 и 64 минуты, всего до восьми попыток. - Остальные ответы
4xxсразу прекращают повторы, поэтому возвращайте4xxтолько для запросов, которые вы не примете никогда. - Доставка «как минимум один раз»: повтор может прийти, когда событие уже обработано, в том числе после перезапуска обработчика Sendalto. Убирайте дубли по
x-sendalto-delivery. - Неудачные доставки видны в «Настройки → API и приватность», там же их можно повторить. На время переезда приёмника подписку можно приостановить.
5. Смена секрета
При смене показывается новый секрет — один раз; им подписываются все следующие попытки, включая повторы прежних событий. Обновите секрет в приёмнике в тот же момент или приостановите подписку на время замены.