Skip to content

Вебхуки ​

Вебхуки дозволяють отримувати HTTP-сповіщення в реальному часі, коли в Looma відбуваються події — нові ліди та повідомлення чату.

Як це працює ​

  1. Ви створюєте вебхук з URL та списком подій
  2. Коли подія відбувається, Looma надсилає POST-запит на ваш URL
  3. Ваш сервер відповідає HTTP 2xx для підтвердження отримання
  4. У разі невдачі Looma повторює спробу з експоненційним відкатом

Підтримувані події ​

ПодіяОпис
lead.createdСтворено новий лід з чату
chat.startedНовий запит на чат
chat.message.newНове повідомлення в чат-сесії

Створення вебхука ​

bash
curl -X POST https://api.looma.chat/v1/webhooks \
  -H "Authorization: Bearer lm_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/looma",
    "events": ["lead.created", "chat.message.new"]
  }'

Відповідь містить secret, який показується лише один раз. Збережіть його надійно — він потрібен для верифікації підписів вебхуків.

Підписка на подію вимагає, крім webhooks:manage, права на читання її даних: leads:read для lead.created, chats:read для chat.started і chat.message.new (інакше 403 scope_denied). site_ids або не передається / null (усі сайти), або непорожній список ID сайтів; порожній список відхиляється з 422. Ключ, обмежений сайтами, підписується лише в їхніх межах.

Формат корисного навантаження ​

http
POST /webhooks/looma HTTP/1.1
Content-Type: application/json
X-Looma-Event: lead.created
X-Looma-Delivery: 25
X-Looma-Timestamp: 1700000000
X-Looma-Signature: sha256=a1b2c3d4e5f6...
json
{
  "id": 451,
  "lead_id": 451,
  "created_at": "2026-01-15T10:00:00+02:00",
  "status": "new",
  "name": "Olena",
  "phone": "067 123 45 67",
  "email": "[email protected]",
  "address": null,
  "lead_text": "Customer interested in Nike Air Max",
  "custom_fields": { "City": "Lviv" },
  "products": [
    {
      "product_id": "1024",
      "title": "Nike Air Max 90",
      "price": 4200.0,
      "currency": "UAH",
      "url": "https://shop.ua/air-max-90",
      "image_url": "https://shop.ua/air-max-90.jpg"
    }
  ],
  "products_text": "Nike Air Max 90 — 4200 UAH",
  "site_id": 8,
  "site_name": "My Shop",
  "site_domain": "shop.ua",
  "source": "telegram",
  "channel_id": 12,
  "channel_name": "@shop_bot",
  "utm_source": null,
  "utm_medium": null,
  "utm_campaign": null,
  "landing_page": null,
  "referrer": null,
  "lead_url": "https://app.looma.chat/leads?id=451",
  "session_id": "s_9f2c41a8",
  "visitor_id": "v_58e51ff36a",
  "collected_fields": { "name": "Olena", "phone": "067 123 45 67", "custom_1729000000000": "Lviv" },
  "product_ids": [1024, 2048]
}

Тіло lead.created — один плаский запис: кожен ключ присутній завжди (null, [] або {}, коли значення немає), тож отримувач мапить поля, не перевіряючи їхню відсутність. Той самий обʼєкт повертає GET /v1/leads?format=flat.

lead.created — знімок у момент створення ліда, зазвичай на першому контакті відвідувача. Поля, які пізніше в тій самій розмові доповнюють цей лід (пошта, ще товари, текст), повторно не надсилаються; GET /v1/leads?format=flat віддає лід таким, яким він є зараз, тож пізніше читання може містити більше, ніж подія.

ПолеТипЗначення
id, lead_idintegerID ліда (обидва ключі мають однакове значення)
created_atstringISO 8601 зі зсувом часового поясу сервера
statusstringСтатус ліда
name, phone, email, addressstring або nullКонтакти; phone — так, як його ввів відвідувач (код країни не додається)
lead_textstring або nullЩо хотів відвідувач
custom_fieldsobjectВласні зібрані поля за назвою з дашборда
productsarrayДо 20 товарів: product_id, title, price, currency, url, image_url
products_textstringОдин рядок назва — ціна валюта на товар, порожній, якщо товарів немає
site_id, site_name, site_domaininteger / string / nullСайт
sourcestringwidget, telegram, viber, facebook, instagram, …
channel_id, channel_nameinteger / string / nullКанал месенджера, якщо є
utm_source, utm_medium, utm_campaign, landing_page, referrerstring або nullДані візиту
lead_urlstringПосилання на лід у дашборді Looma
session_id, visitor_idstringЧат-сесія й відвідувач
collected_fieldsobjectЗібрані поля як збережено
product_idsarrayID товарів як збережено (числа лишаються числами)

Зворотна сумісність

lead_id, site_id, session_id, visitor_id, channel_id, lead_text, collected_fields, product_ids і created_at зберігають назви й типи, які були до плаского формату, — наявні отримувачі працюють далі. Решту полів додано поверх. Якщо плаский запис для ліда зібрати не вдалося, Looma надсилає початкове коротке тіло.

Решта подій (chat.started, chat.message.new) мають власні навантаження.

Дедуплікація ​

X-Looma-Delivery — це ID доставки, і він змінюється при кожному повторі, тож лід за ним не впізнати. Дедуплікуйте lead.created за id ліда.

Верифікація підписів ​

Кожен запит вебхука підписаний HMAC-SHA256. Завжди перевіряйте підпис, щоб впевнитися, що запит надійшов від Looma.

Підпис обчислюється як:

HMAC-SHA256(timestamp + "." + payload, secret)

Приклад PHP ​

php
$payload = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_LOOMA_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_LOOMA_SIGNATURE'] ?? '';

// Заголовок несе префікс `sha256=` — зніміть його перед порівнянням.
$signature = str_starts_with($signature, 'sha256=') ? substr($signature, 7) : $signature;

$expected = hash_hmac('sha256', $timestamp . '.' . $payload, $webhookSecret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

// Опціонально: відхиляти запити старше 5 хвилин
if (abs(time() - (int)$timestamp) > 300) {
    http_response_code(401);
    exit('Timestamp too old');
}

$data = json_decode($payload, true);
// Обробка події...

Приклад JavaScript ​

javascript
const crypto = require('crypto');

// Підпис рахується від СИРОГО тіла. express.json() розбере його наново, а
// повторний JSON.stringify змінить порядок ключів і екранування Unicode —
// HMAC не зійдеться ніколи. Тому беремо сирі байти.
app.post('/webhooks/looma', express.raw({ type: 'application/json' }), (req, res) => {
  const payload = req.body.toString('utf8');
  const timestamp = req.headers['x-looma-timestamp'];
  const signature = (req.headers['x-looma-signature'] || '').replace(/^sha256=/, '');

  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(timestamp + '.' + payload)
    .digest('hex');

  const ok =
    signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

  if (!ok) {
    return res.status(401).send('Invalid signature');
  }

  // Назва події — у заголовку, а не в тілі.
  const event = req.headers['x-looma-event'];
  const data = JSON.parse(payload);
  console.log(`Received: ${event}`, data);

  res.status(200).send('OK');
});

Політика повторних спроб ​

Якщо ваш сервер не відповідає HTTP 2xx, Looma повторює з експоненційним відкатом:

СпробаЗатримка
1-а повторна30 секунд
2-а повторна2 хвилини
3-я повторна10 хвилин

Після 3 невдалих спроб доставку помічено як невдалу. Після 10 послідовних невдач різних доставок вебхук автоматично деактивується.

Деактивований вебхук можна повторно увімкнути через API або дашборд.

Відповідь 410 Gone ​

Якщо ваш ендпоінт відповідає HTTP 410 Gone, Looma сприймає це як «відписано»: вебхук одразу видаляється, без повторів. Так Zapier повідомляє Looma, що Zap вимкнено чи видалено. Відповідайте 410 лише тоді, коли вебхук справді більше не потрібен; будь-який інший не-2xx статус іде за політикою повторів вище.

Тестування ​

Використовуйте тестовий ендпоінт для надсилання зразкового навантаження:

bash
curl -X POST https://api.looma.chat/v1/webhooks/5/test \
  -H "Authorization: Bearer lm_your_key"

Це надсилає тестову подію на URL вебхука та повертає статус відповіді від вашого сервера.

Ліміти ​

  • Максимум 20 вебхуків на акаунт
  • Таймаут доставки: 10 секунд
  • Максимальний розмір навантаження: ~64 КБ

Looma.chat — AI-пошук і чат для інтернет-магазинів