Вебхук - це POST, який GREM надсилає на ваш URL, коли щось сталося: завершилося завдання, змінився об'єкт або лід. Керування підписками потребує області webhooks і не тарифікується. Вебхуками агентства керує ключ усього агентства; ключ агента отримує 403.
Підписатися
curl -X POST https://developers.grem.capital/api/v1/webhooks \
-H "Authorization: Bearer gsk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/grem-hook","events":["content.completed","content.failed","lead.created"]}'
{ "id": "...", "url": "https://example.com/grem-hook", "events": [...], "status": "active",
"failureCount": 0, "lastDeliveryAt": null, "secret": "whsec_..." }
url- публічна адресаhttps://на стандартному порту (443), безuser:passwordв адресі. Інші порти, логін і пароль в адресі, приватні, локальні та внутрішні адреси відхиляються (400). Правило порту діє, коли ви задаєте адресу: адреси, збережені раніше з іншим портом, і далі отримують доставки.events- хоча б одна подія з переліку нижче; невідомі назви відкидаються.secret- секрет підпису. Він повертається тільки один раз, збережіть його.- На один акаунт - до 10 адрес і окремо до 10 адрес пісочниці, створених тестовими ключами; 11-та відхиляється з
422 business_rule. - Адреси, створені тестовим ключем, - адреси пісочниці: вони отримують лише події завдань пісочниці (Пісочниця).
Підписки можна вести і в кабінеті: API → Вебхуки. Там же можна змінити секрет підпису.
Керування
| Запит | Що робить |
|---|---|
GET /webhooks | перелік ваших підписок (без секретів) |
PATCH /webhooks/{id} | змінити url, events або status (active / disabled) |
DELETE /webhooks/{id} | видалити підписку |
POST /webhooks/{id}/test | надіслати пробну подію; тіло {"eventType": "content.completed"} задає форму прикладу |
GET /webhooks/{id}/deliveries | 50 останніх доставок: статус, код відповіді, затримка, початок відповіді |
POST /webhooks/{id}/deliveries/{deliveryId}/resend | надіслати доставку ще раз; deliveryId — _id рядка з журналу |
PATCH без жодної допустимої події в events або зі status, відмінним від active / disabled, відхиляється з 400. DELETE невідомої підписки відповідає 404. У журналі доставок payload - це data події.
Події
Завершення завдань
| Подія | Коли |
|---|---|
valuation.completed, valuation.failed | повний звіт про оцінку |
review.completed, review.failed | перевірка звіту |
content.completed, content.partial, content.failed | пакет контенту (partial - частина файлів не вийшла) |
text.completed, text.failed | тексти |
video.completed, video.failed | відео |
avatar.completed, avatar.failed | відео з аватаром |
photo.completed, photo.failed | покращення фото |
floorplan.completed, floorplan.failed | планування |
pdf.completed, pdf.failed | PDF-презентація |
data: { "jobId", "status", "state" }. status - вихідний статус інструмента у верхньому регістрі (наприклад, COMPLETED); розгалужуйтеся за state. Якщо завдання не завершилося за 30 хвилин - .failed з "reason": "timeout". Завдання відстежується, лише якщо підписка на його подію існувала в момент створення завдання (Завдання і результати).
Об'єкти
| Подія | Коли |
|---|---|
object.created | ви створили об'єкт |
object.updated | об'єкт змінено, у тому числі модератором |
object.published | об'єкт став публічним |
object.deleted | чернетку видалено або опублікований об'єкт знято в архів |
data: { "objectId", "kind", "status", "occurredAt" }; у object.deleted замість status - mode (deleted або archived). Повні дані - через GET /objects/{kind}/{id}.
Ліди
| Подія | Коли |
|---|---|
lead.created | новий лід, адресований вам |
lead.updated | змінився статус або відповідальний |
data: { "leadId", "status", "type", "source", "objectId", "occurredAt" }. Персональних даних клієнта в події немає - повний лід через GET /leads/{id}.
Swipe Match
| Подія | Коли |
|---|---|
match.lead.offered | вам (або агенту вашого агентства) запропоновано лід |
match.lead.offer_closed | пропозиція закрилася без вас (вийшов час) |
match.lead.matched | лід закріплено за вами |
match.lead.updated | лід перейшов на новий етап |
match.lead.unassigned | лід знято з вас |
data: { "leadId", "eventId", "eventCode", "occurredAt", "source", "stage", "active", "leadKind", "clientType", "country", "brokerId", "agencyId" }. Імені, телефону і пошти клієнта в подіях немає (Swipe Match).
Формат доставки
POST https://example.com/grem-hook
Content-Type: application/json
x-grem-event: content.completed
x-grem-signature: t=1759400000,v1=3b78a03b78058d229023db2a0b4ee072...
{ "id": "...", "type": "content.completed", "createdAt": "2026-10-02T10:00:00.000Z", "data": { ... } }
idоднаковий в усіх повторах однієї доставки - використовуйте його, щоб не обробити подію двічі.tіcreatedAt- момент конкретної спроби.- Тестова доставка (
POST /webhooks/{id}/test) зeventTypeз переліку несеtest: trueуdata; без нього тип події -webhook.test.eventType, якого немає в переліку, -400. Самwebhook.testприймається.
Перевірка підпису
Підпис - HMAC-SHA256 від рядка "<t>.<сире тіло>" з секретом підписки, у шістнадцятковому вигляді в v1. Перевіряйте до будь-якої обробки і відкидайте застарілі t (захист від повторного надсилання).
Рахуйте підпис від сирого тіла запиту, рівно як воно прийшло, до розбору JSON. Повторна серіалізація змінює байти, і підпис не збіжиться.
const crypto = require('crypto');
function verifyGremSignature(secret, header, rawBody, toleranceSec = 300) {
const parts = Object.fromEntries(String(header || '').split(',').map((kv) => kv.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post('/grem-hook', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8');
if (!verifyGremSignature(process.env.GREM_WEBHOOK_SECRET, req.get('x-grem-signature'), raw)) {
return res.status(400).send('invalid signature');
}
const event = JSON.parse(raw);
// event.id, event.type, event.data
res.sendStatus(200);
});
У SDK те саме робить verifyWebhookSignature().
Повтори і вимкнення
- Доставка успішна, якщо ваш сервер відповів
2xxпротягом 10 секунд. Відповідайте швидко, важку роботу робіть у фоні. - Невдала доставка повторюється: через 5 хвилин, 30 хвилин, 2 години і 6 годин - разом до 5 спроб.
- Після 5 поспіль повністю невдалих доставок підписка вимикається (
status: disabled). Увімкніть її знову черезPATCH /webhooks/{id}з{"status":"active"}або в кабінеті. - Журнал доставок (
GET /webhooks/{id}/deliveries) показує код відповіді вашого сервера і початок тіла відповіді - з нього видно, чому доставка не пройшла.
Пов'язані статті
- Завдання і результати - стани завдань.
- Swipe Match - коди подій лідів модуля.