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

Выгрузка данных и 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

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

{
"schema_version": 1,
"event_id": "0f6c2a51-8d3e-4b7a-9c1f-5e2d7a4b8c90",
"event": "quiz_session_completed",
"occurred_at": "2026-10-02T14:07:53.104Z",
"quiz": {
"id": "EF80841F3C2A9B71",
"title": "Основы кибербезопасности",
"type": "test"
},
"session": {
"id": "140ec66e-f3e6-4343-90c7-c94c134c0f12",
"started_at": "2026-10-02T14:06:32.413Z",
"completed_at": "2026-10-02T14:07:52.912Z",
"duration_sec": 80,
"language": "ru",
"is_broadcast": false,
"utm": {"s": "ad"}
},
"bot": {
"id": "5B0E91C47D2F8A36",
"username": "my_company_bot"
},
"respondent": {
"id": "R-7K2M9QX4HD3WZ1PB",
"username": "ivan_petrov",
"name": "Иван",
"full_name": "Иван Петров",
"phone": null
},
"result": {
"kind": "passed",
"answered": 2,
"score": 3,
"correct": 1,
"scored_questions": 1,
"passed": true,
"variables": {"risk": 2},
"ending": {"code": "E1", "title": "Отличный результат!", "outcome": "expert"}
},
"answers": [
{
"question": "Q1",
"title": "Какой пароль надёжнее?",
"type": "single_choice",
"skipped": false,
"value": ["A2"],
"custom_text": null,
"text": "Длинная фраза из слов",
"correct": true,
"weight": 3,
"answered_at": "2026-10-02T14:06:41.083Z",
"time_ms": 4118
},
{
"question": "Q2",
"title": "Что ещё используете?",
"type": "multi_choice",
"skipped": false,
"value": ["A1", "A3"],
"custom_text": "Аппаратный ключ",
"text": "Менеджер паролей; Двухфакторный вход; Аппаратный ключ",
"correct": null,
"weight": 0,
"answered_at": "2026-10-02T14:06:51.720Z",
"time_ms": 9425
}
]
}

Коды вместо внутренних номеров​

Внутренних номеров базы в документе нет — ни опроса, ни бота, ни Telegram id участника.

ЧтоКак выглядитКак устроено
Опрос, ботEF80841F3C2A9B71Тот же код, что в адресной строке кабинета: опрос в документе можно сопоставить с тем, что вы видите в браузере
РеспондентR-7K2M9QX4HD3WZ1PBПсевдоним. Один человек во всех опросах вашей компании получает один и тот же код — по нему склеиваются его прохождения. Обратно в Telegram id он не превращается
ВопросQ1, Q2…Номер шага опроса — тот же, что в воронке аналитики и в Excel-выгрузке. Ремарка без ответа номер занимает, но в документ не входит, поэтому номера могут идти с пропуском
Вариант ответаA1, A2…Номер варианта внутри вопроса, сверху вниз
ФиналE1, E2…Номер финала в опросе

Номера Q, A и E считаются по текущему устройству опроса. Если после сбора ответов переставить вопросы или варианты, номера сдвинутся — поэтому рядом с кодом всегда приходит текст (title, text).

Что в полях​

Верхний уровень

ПолеЧто означает
schema_versionВерсия формата документа. Поменяется только при несовместимых изменениях
event_idИдентификатор события. Доставка «как минимум один раз»: при повторе приходит тот же event_id — по нему отсеивайте дубли
eventЧто произошло. Сейчас приходит одно событие — quiz_session_completed, «респондент завершил опрос»
occurred_atКогда событие поставлено в очередь, UTC

quiz — id (код, см. выше), title — название, type — тип опроса: survey (простой опрос), quiz (квиз с баллами), test (тест с порогом).

session — прохождение. id — uuid прохождения (у рассылки он общий на всех участников, поэтому различайте события по event_id), started_at и completed_at — начало и конец (UTC; у рассылки начало — когда участник присоединился), duration_sec — длительность в секундах, language — язык участника в Telegram, is_broadcast — пришло ли прохождение из рассылки, utm — метки из ссылки, по которой человек пришёл в бота (пусто, если меток не было; как их задать — в статье Метки в ссылке).

bot — id (код) и username — адрес бота в Telegram.

respondent — участник: id (псевдоним), username, name, full_name — как они записаны в Telegram, phone — телефон, если участник им поделился. Чего человек о себе не сообщил, приходит как null.

result — итог.

ПолеЧто означает
kindsurvey — опрос без оценки; scored — квиз с баллами; passed / failed — тест сдан или нет
answeredСколько вопросов получили ответ
scoreНабранный балл — сумма весов, которые вы задали вариантам. Только у квиза и теста
correctСколько ответов верных. Только у квиза и теста
scored_questionsСколько вопросов оценивалось. Только у квиза и теста
passedДля теста — сдан ли, для квиза — прошёл ли порог. Только у квиза и теста
variablesВаши переменные опроса с итоговыми значениями, например баллы по шкалам типологии
endingФинал, на котором закончилось прохождение: code, title (первая строка текста финала), outcome (метка финала, если вы её задали)

answers — ответы на вопросы, которые участник увидел. Вопросов из другой ветки или после того, как человек бросил опрос, в списке нет: «не видел» и «пропустил» — разные вещи.

ПолеЧто означает
question / titleКод вопроса и его текст
typesingle_choice, multi_choice, boolean, rating, text, date, time, datetime
skippedУчастник нажал «Пропустить». Тогда value и weight — null
valueОтвет для машины: список кодов вариантов для выбора, true/false, число для шкалы, строка ISO для даты, текст
custom_textСвой вариант, который участник дописал к выбору
textОтвет так, как его видел участник: подписи вариантов через «; »
correctВерен ли ответ. null, если у вопроса нет правильного варианта
weightНачисленный за ответ вес
answered_at / time_msКогда ответил (UTC) и сколько думал, в миллисекундах

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

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

Подпись​

Если вы зададите секрет, тело подписывается 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-файлом в вашей папке на Диске. В файле тот же документ, что получает свой webhook: опрос, респондент, итог и ответ на каждый вопрос, а не выжимка в колонки таблицы. Имя файла — дата события и начало его event_id: повторная доставка перезаписывает тот же файл, а не плодит копии. Удобно как архив — папка синхронизируется на компьютер обычным приложением Диска.

  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На Диске закончилось место

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

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

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

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