Перейти к основному содержимому

Выгрузка данных и CRM

Google Таблицы

Каждое прохождение опроса дописывается строкой в вашу таблицу: когда, какой опрос, кто и с каким результатом. Дальше вы работаете с этим как с обычной таблицей — сводные, фильтры, графики.

Подключается ключом сервисного аккаунта, без входа через Google: мы не просим доступ к вашему аккаунту, а пишем от имени отдельного «робота», которому вы сами открываете конкретную таблицу.

Шаг 1. Включить Sheets API

  1. Откройте console.cloud.google.com и создайте проект (или выберите существующий).
  2. «APIs & Services» → «Library» → найдите «Google Sheets API» → «Enable».

Без этого шага запросы будут отбиваться с ошибкой «API has not been used in project».

Шаг 2. Создать сервисный аккаунт и ключ

  1. «APIs & Services» → «Credentials» → «Create credentials» → «Service account».
  2. Имя любое, роли на этом шаге не нужны — доступ выдаётся не здесь, а в самой таблице.
  3. Откройте созданный аккаунт → вкладка «Keys» → «Add key» → «Create new key» → формат JSON. Файл скачается сам.

Шаг 3. Открыть таблицу этому аккаунту

  1. Откройте JSON-файл и найдите поле client_email — там адрес вида …@….iam.gserviceaccount.com.
  2. В Google-таблице: «Настройки доступа» → вставьте этот адрес → права «Редактор» → «Отправить».

Это самый частый пропущенный шаг: без него Google отвечает 403, и в журнале интеграции будет «нет доступа к таблице».

Шаг 4. Подключить

  1. Идентификатор таблицы — часть её адреса между /d/ и /edit.
  2. В карточке «Google Таблицы» вставьте содержимое JSON-файла целиком (вместе с фигурными скобками) и идентификатор таблицы.

Строки дописываются в конец и ничего не перезаписывают, поэтому ваши формулы и сводные не сломаются. Порядок колонок фиксированный: дата, событие, опрос, сессия, участник, результат, максимум.

Если получаете ошибку доступа

Почти всегда это значит, что таблицу не открыли сервисному аккаунту. Проверьте, что адрес из client_email есть в списке доступа с правом редактирования.

Bitrix24

Прохождение опроса становится лидом в вашей CRM — с именем участника, названием опроса и результатом в описании.

Подключается входящим вебхуком портала, без установки приложения.

  1. В Bitrix24: «Приложения» → «Разработчикам» → «Другое» → «Входящий вебхук».
  2. В списке прав отметьте CRM (crm) — остальные права не нужны, лид заводится только им.
  3. Сохраните и скопируйте адрес целиком, вместе с завершающим слэшем: https://ваш-портал.bitrix24.ru/rest/1/токен/.
  4. Вставьте его в карточку «Bitrix24 — лид из опроса».

Если портал отвечает INVALID_CREDENTIALS — у вебхука нет права crm. Method not found означает, что адрес скопирован не полностью (обычно теряется завершающий слэш).

В настройках можно задать заголовок лида (подстановка {quiz} — название опроса) и код источника из вашей CRM.

Заведение лида не запускает автоматизацию портала, пока вы сами её не настроите: робот не должен дёргать те же сценарии, что заявка от человека.

Свой webhook

Если нужного сервиса нет в каталоге — есть универсальный вариант. Мы отправляем POST с JSON события на любой ваш адрес: пишите в свою базу, запускайте сценарий в Zapier или n8n, дёргайте внутренний сервис.

Как выглядит запрос

POST /your/endpoint HTTP/1.1
Content-Type: application/json
X-Pollagram-Signature: sha256=6f1a…
X-Pollagram-Timestamp: 1755471975949

Тело — событие целиком:

{
"description": "Респондент завершил опрос",
"quiz_id": 183,
"session_id": "140ec66e-f3e6-4343-90c7-c94c134c0f12",
"company_id": "22222222-2222-4222-8222-222222222222",
"quiz_title": "Основы кибербезопасности",
"participant_name": "Иван Петров",
"score": 1,
"max_score": 4,
"chat_id": 123456789,
"bot_id": 25,
"is_broadcast": false
}

Что в полях

ПолеТипВсегда ли естьЧто означает
descriptionстрокадаСобытие человеческими словами
quiz_idчислодаИдентификатор опроса
session_iduuidдаПрохождение — по нему события связываются между собой
company_iduuidдаВаша компания
quiz_titleстрокапочти всегдаНазвание опроса
participant_nameстроканетИмя участника; отсутствует, если он не представился
score / max_scoreчисланетРезультат; у опросов без правильных ответов их нет
chat_idчислонетЧат участника в Telegram
bot_idчислонетБот, через которого шло прохождение
is_broadcastда/нетнетРассылка на многих. У такой сессии событие одно на всех, а участников много — поля выше относятся к одному из них

Полей может стать больше — новые мы добавляем, старые не убираем и не переименовываем. Разбирайте тело так, чтобы незнакомое поле его не ломало.

Другие события устроены так же. У «респондент начал опрос» и «в боте появился новый участник» структура тела та же, отличается description и набор заполненных полей: у начала прохождения ещё нет результата, у нового участника — опроса. Отличать события удобнее по точке, на которую вы подписались, чем по содержимому.

Подпись

Если вы зададите секрет, тело подписывается HMAC-SHA256, и подпись приходит в X-Pollagram-Signature вместе с меткой времени X-Pollagram-Timestamp (миллисекунды UTC). Проверяйте её: без проверки любой, кто узнал ваш адрес, сможет присылать вам что угодно. Метка времени — против повторной отправки перехваченного запроса, отвергайте всё старше нескольких минут.

Подпись считается по сырому телу запроса, а не по разобранному и заново собранному JSON: любая перестановка ключей даст другой результат.

import hashlib
import hmac

def is_ours(raw_body: bytes, header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(f'sha256={expected}', header)

Что мы считаем успехом

ОтветКак понимаем
2xxДоставлено, событие закрыто
3xxОтказ. За редиректами не идём: перенаправление обходит проверку адреса
4xx, 5xxОтказ, попробуем ещё раз
Нет ответаОтказ, попробуем ещё раз

Отвечайте быстро и не делайте тяжёлую работу внутри обработчика: примите запрос, положите в очередь у себя, ответьте 200.

Если ваш сервис не ответил

Мы повторяем до пяти раз с растущей паузой — это около получаса. Дальше событие уходит в отбракованные, а в кабинете появляется уведомление. Дольше держать смысла нет: если сервис не поднялся за полчаса, он не поднимется и за час, а событие к тому времени уже неактуально.

Адрес должен быть доступен из интернета

Локальные адреса и адреса внутренних сетей отклоняются: запрос уходит с наших серверов, и мы не отправляем его внутрь чужой инфраструктуры. Имя резолвится на нашей стороне, поэтому internal.example.com, указывающий на 10.0.0.5, тоже будет отклонён.

Notion

Каждое прохождение становится страницей в вашей базе Notion. Дальше это ваши обычные данные: представления, фильтры, связи с другими базами.

Что понадобится

  1. Внутренняя интеграция в notion.so/my-integrations и её токен вида ntn_….

  2. База данных с колонками — имена важны, мы пишем именно в них:

    КолонкаТипЧто попадает
    NameTitleНазвание опроса
    УчастникTextИмя или идентификатор
    РезультатNumberНабранные баллы
    МаксимумNumberСколько было возможно
    КогдаDateВремя прохождения
  3. Доступ интеграции к базе. Откройте базу → «…» в правом верхнем углу → Connections → добавьте свою интеграцию.

Третий пункт — самая частая причина, по которой ничего не работает: токен верный, база существует, а Notion отвечает «не найдено», потому что интеграция её не видит. Мы распознаём этот случай и пишем в журнале подсказку целиком.

Колонок может не хватать — те, что есть, заполнятся, на остальные мы не ругаемся: иначе переименование колонки ломало бы выгрузку.

Airtable

То же самое, но в Airtable: строка на каждое прохождение, только дописывание.

Что понадобится

  1. Персональный токен в airtable.com/create/tokens с правом data.records:write и доступом к нужной базе.
  2. Идентификатор базы — часть адреса вида app….
  3. Название таблицы и поля: Опрос, Участник, Результат, Максимум, Когда.

Типы полей Airtable приводит сам, поэтому менять «Результат» с числа на текст и обратно можно без оглядки на выгрузку.

Яндекс Диск

Каждое прохождение сохраняется отдельным JSON-файлом в вашей папке на Диске: полные данные события, а не выжимка в колонки таблицы. Удобно как архив — папка синхронизируется на компьютер обычным приложением Диска.

  1. Откройте oauth.yandex.ru → «Создать приложение».
  2. В правах найдите «Яндекс Диск REST API» и отметьте cloud_api:disk.write и cloud_api:disk.app_folder.
  3. В способах авторизации отметьте «Веб-сервисы»; Redirect URI можно указать https://oauth.yandex.ru/verification_code.
  4. Сохраните приложение и нажмите «Отладочный токен» — Яндекс выдаст готовый OAuth-токен. Полный флоу с redirect и refresh-токенами здесь не нужен: токен приносите вы, а не мы его запрашиваем от вашего имени.
  5. Вставьте токен в карточку «Яндекс Диск». Папку укажите свою или оставьте поле пустым — создадим Pollagram в корне Диска.

Файл называется по времени и сессии, а перезапись включена намеренно: повтор доставки не плодит дубли.

Ответ ДискаЧто это значит
401Токен истёк или отозван — выпустите новый
403У токена нет прав на запись
507На Диске закончилось место

Область действия: не всем опросам сразу

По умолчанию интеграция срабатывает на всех опросах компании. Ограничить можно в её настройках, поле «Область действия»:

  • Вся компания — как раньше, любой опрос и любой бот.
  • Конкретный опрос — нужен код опроса. Он показан в редакторе опроса, блок «Интеграция» в самом низу настроек, там же кнопка «Скопировать».
  • Конкретный бот — код бота, аналогично в настройках бота.

Код зашифрован и не совпадает с номером в базе. Код бота, вставленный в поле опроса, не подойдёт — так и задумано: иначе настройка молча привязалась бы к постороннему объекту.