Docs

Вебхуки

Останнє оновлення: 2026-10-035 хв читання

Вебхук - це 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}/deliveries50 останніх доставок: статус, код відповіді, затримка, початок відповіді
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.failedPDF-презентація

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) показує код відповіді вашого сервера і початок тіла відповіді - з нього видно, чому доставка не пройшла.

Пов'язані статті

Ця стаття була корисною?