Меню

Центр документации

Интеграции и API Recca

Один справочник по всем способам подключения: no-code формы, headless API и виджеты. Выберите модель в обзоре — дальше по разделам.

Обзор · выбор модели

Recca подключается тремя способами. Все три можно совмещать — выбор зависит от того, есть ли у вас бэкенд и где должен жить кабинет партнёра. Виджеты — надстройка над тем же API, а не отдельная «настоящая» интеграция.

МодельКомуКодГде кабинетРаздел
Сайт (no-code) Tilda / Creatium / конструктор без бэкенда Нет кода — webhook + токен В кабинете Recca («Лиды») Сайт
Headless API B2B со своим бэкендом и логином S2S вызовы federation API Свой UI тенанта Headless
Виджеты (SDK) Кто хочет готовый кабинет быстро Drop-in <recca-*> теги Встроен в страницу тенанта Виджеты
Правило трёх событий. Независимо от модели, атрибуция держится на трёх вызовах: регистрация → users/upsert (+ лид), заявка-форма → /leads, оплата → /conversions. Подробнее — Атрибуция.

Установка

Для headless/виджетов — одна команда в корне репозитория (ставит SDK + кладёт Claude-агента):

curl -fsSL https://recca.ru/integration/install.sh | sh

Или вручную через npm (SDK из CDN-тарболлов — публичного npm-scope пока нет):

npm i \
  https://api.recca.ru/cdn/embed-components/recca-embed-components-0.1.3.tgz \
  https://api.recca.ru/cdn/packages/recca-federation-sdk-1.2.0.tgz

No-build вариант (только виджеты, без npm) — drop-in тег:

<script type="module" src="https://api.recca.ru/cdn/embed-components/v1.js"></script>

Для no-code модели (Tilda/Creatium) устанавливать ничего не нужно — см. раздел «Сайт».

Модель 1 · Интеграция с сайтом (no-code)

Для конструкторов без бэкенда (Tilda, Creatium) или любой формы. Заявки с форм автоматически попадают во вкладку «Лиды» магазина и привязываются к нужному офферу. Кода писать не нужно — только токен и настройка вебхука в конструкторе.

⚠️ Витринные лиды ≠ кабинет партнёра. Лиды с /hooks/leads создаются с organization_id = NULL и видны только в админ-вкладке «Лиды» магазина. В федеративный кабинет партнёра (kpi.leads_count, recent-leads виджеты) они не попадают — тот считает лиды по organization_id = вашего org. Если вам нужна атрибуция в кабинет партнёра — используйте Headless APIPOST /federation/v1/leads (см. ниже в Атрибуции).

Шаг 1 — создать интеграцию в кабинете

В app.recca.ru/org/<slug>/integrations/website → «Создать интеграцию»: выберите платформу (Tilda / Creatium / Другая), опционально URL и название, и настройте маппинг форм → офферов (имя формы → offer_id). Recca выдаст токен один раз — сохраните (дальше виден только хвост token_hint). Доступно на тарифе Pro и выше.

Шаг 2 — направить вебхук формы на Recca

В конструкторе укажите webhook-приёмник:

POST https://api.recca.ru/hooks/leads
Заголовок:  X-Recca-Token: <ваш-токен>
            (или Authorization: Bearer <токен>, или ?token=<токен>)
  • Tilda: Сайт → Формы → Webhook → URL https://api.recca.ru/hooks/leads?token=…. Шлёт плоский объект с ключами Name / Phone / Email / Comment / Promo.
  • Creatium: интеграция «Вебхук» на отправку заявки. Шлёт вложенный order.fields + page.query.recca.
  • Другая (custom): структурированный JSON (ниже).

Шаг 3 — атрибуция к рефереру

Чтобы лид привязался к партнёру, в форму/URL должен попасть deeplink-код Recca:

  • Tilda — скрытое поле recca со значением кода (или код вида REC… в любом поле).
  • Creatium — код берётся из ?recca=… в URL страницы (или из referer).
  • Промо-код (Promo / promo_code) тоже резолвит оффер через маппинг.

Приоритет привязки оффера: form_offer_mapping[имя формы]form_offer_mapping[промо] → deeplink-код (даёт оффер и реферера) → если ничего, создаётся лид уровня магазина.

Формат «custom»

POST https://api.recca.ru/hooks/leads
X-Recca-Token: <токен>
Content-Type: application/json

{
  "formName": "consult",
  "contact": { "name": "Иван", "phone": "+79991234567",
               "email": "i@ex.ru", "comment": "..." },
  "metadata": { "promo_code": "SUMMER", "deeplink_code": "tUPLcT7DF6L4" }
}

Проверка

curl -i -X POST https://api.recca.ru/hooks/leads \
  -H "X-Recca-Token: <токен>" -H "Content-Type: application/json" \
  -d '{"contact":{"name":"Smoke","phone":"+79990000000"}}'
#  -> 200 { "success": true, "data": { "lead_id": "lead_...", "duplicate": false } }

Идемпотентность: повтор того же payload в пределах интеграции вернёт тот же lead_id (duplicate: true). Токен можно ротировать/отзывать в том же разделе кабинета.

Модель 2 · Headless API (своя интеграция)

Каноничная модель (Model B). Тенант авторизует пользователей нативно у себя, мостит идентичность через S2S и рисует кабинет в своём UI. Пользователь Recca не видит. Нужен ключ rtk_live_… со скоупами users:upsert + users:impersonate (+ leads:write, conversions:write).

Шаг 1 — мост идентичности

const { data } = await recca.users.upsert({
  first_name, last_name,
  vk_id, yandex_id, telegram_id, phone, email, email_verified,
  recca_ref: cookies.recca_ref,        // атрибуция, если пришёл по реф-ссылке
})
db.users.update(localId, { recca_user_id: data.tenant_recca_id })  // сохранить FK

Шаг 2 — рисуете кабинет своим UI

Со своего бэкенда дёргаете (всё под users:impersonate, по сохранённому :id = tenant_recca_id):

Что нужноЭндпоинт
Реф-ссылкаPOST /federation/v1/users/:id/referral-links → url + short_url
Дерево рефераловGET /federation/v1/users/:id/tree (+ /tree/stats)
Лиды (CRM)GET /federation/v1/users/:id/leads
ПрофильGET /federation/v1/users/:id
Баланс / KPIGET /embed/v1/data/cabinet/:id?tenant=<slug> (заголовок Origin из allowed_origins)

Ответы — JSON, рендерите в своих компонентах. Всё обёрнуто в { success, data } — читайте body.data (SDK разворачивает).

Шаг 3 — атрибуция событий

Регистрация → users/upsert + /leads (тот же recca_ref); оплата → /conversions/register. См. Атрибуцию.

Инструмент — @recca/federation-sdk (Node S2S), без @recca/embed-components. Ключ rtk_live_… только на сервере, никогда в браузере.

Модель 3 · Виджеты — встроенный кабинет

Готовые web-components (@recca/embed-components, Shadow DOM — CSS сайта не протекает). Кабинет встраивается в страницу тенанта без редиректа на app.recca.ru. Импортируйте SDK один раз в layout.

Flow B (рекомендуется — у тенанта свой логин)

Бэкенд минтит ссылку, виджет показывает — без Recca-попапа:

// бэкенд
const { short_url } = await recca.referralLinks.create(tenantReccaId, { offer_id })

// страница
<recca-referral-display id="rd" theme="visitka"></recca-referral-display>
<script>document.getElementById("rd").setAttribute("url", short_url)</script>

Кабинет

<script type="module">import "@recca/embed-components"</script>

<recca-cabinet-kpi   tenant="<slug>" user-id={tenantReccaId} theme="visitka"></recca-cabinet-kpi>
<recca-bonus-balance tenant="<slug>" user-id={tenantReccaId}></recca-bonus-balance>
<recca-recent-leads  tenant="<slug>" user-id={tenantReccaId}></recca-recent-leads>
<recca-referral-tree tenant="<slug>" user-id={tenantReccaId} max-depth="3"></recca-referral-tree>

Vue: добавьте compilerOptions.isCustomElement: t => t.startsWith('recca-'). Виджеты ходят браузером в origin-gated /embed/v1/data/* — добавьте свой домен в allowed_origins. Flow A (<recca-referral-input> с попапом) — только если единственный логин на сайте это Recca.

Атрибуция · 3 события

Событие у васЭндпоинтЧто создаётся
Регистрация (любой провайдер)POST /federation/v1/users/upsert + recca_refuser + membership + рёбра дерева
Регистрация (тот же handler)POST /federation/v1/leads (тот же recca_ref)lead-строка (Recent Leads + KPI)
ОплатаPOST /federation/v1/conversions/registerконверсия + каскад бонусов

Регистрация = два вызова (upsert + leads). Без второго новый юзер появится в дереве, но kpi.leads_count = 0 и Recent Leads пусты. recca_ref ловите из ?recca_ref= на лендинге в cookie (30 дней):

const ref = new URL(location.href).searchParams.get("recca_ref")
if (ref) document.cookie = `recca_ref=${ref}; path=/; max-age=2592000; samesite=lax`

Приоритет матчинга идентичности: vk_id → yandex_id → telegram_id → phone → email. Передавайте всё что есть.

Куда попадёт лид — выбор эндпоинта

Два эндпоинта для лидов наполняют разные кабинеты — частая путаница:

ЭндпоинтЗаголовокАтрибуцияorg_idГде виден лид
POST /hooks/leads X-Recca-Token deeplink_code (сырой код) / промо NULL Админ-вкладка «Лиды» магазина
POST /federation/v1/leads X-Recca-Tenant-Key recca_ref (signed-ref) = ваш org Кабинет партнёра (kpi.leads_count, recent-leads)

Для атрибуции в кабинет партнёра — только federation-эндпоинт:

POST https://api.recca.ru/federation/v1/leads
X-Recca-Tenant-Key: rtk_live_...           // НЕ X-Recca-Token!
Content-Type: application/json

{
  "recca_ref":   "<signed-ref>",           // длинный токен из ?recca_ref= на лендинге
  "external_id": "form:<ваш-id>",          // детерминированный -> идемпотентность
  "lead_source": "form_submit",
  "contact":     { "name": "...", "phone": "+7...", "email": "..." }
}
// -> 201 { data: { lead_id, referrer_user_id } }
Что такое recca_ref: длинный signed-ref — значение ?recca_ref=<token>, которое Recca ставит на лендинг при клике по реф-ссылке (то же, что signed_ref из минта и что вы кладёте в users/upsert). НЕ короткий recca.ru/d/<code>, НЕ голый код деплинка. form_offer_mapping для federation-атрибуции не нужен — это только для /hooks/leads.

Реферер должен быть активным участником вашего org (иначе 403 BADGE_NOT_ACTIVE; авто-бейдж при users/upsert это закрывает), и recca_ref должен быть выписан для вашего org (иначе TENANT_MISMATCH).

Синхронность: запись лида + referrer_id + organization_id происходит синхронно в запросе (под локом, идемпотентно по (org, external_id)). Но /embed/v1/data/cabinet отдаётся с Cache-Control: private, max-age=60 — KPI может отставать до 60 секунд. Проверить мгновенно без кэша: GET /federation/v1/users/<referrer_id>/leads (X-Recca-Tenant-Key).

Webhooks

Recca шлёт события на ваш URL (настраивается в /org/<slug>/integration): lead.created, bonus.pending, bonus.approved, bonus.rejected, conversion.cascaded, conversion.reversed. Подпись HMAC-SHA256 в X-Recca-Signature.

app.post("/api/recca/webhook",
  express.raw({ type: "application/json" }),   // ВАЖНО: raw, не express.json()
  (req, res) => {
    const sig = req.header("X-Recca-Signature") || ""
    const expected = crypto.createHmac("sha256", process.env.RECCA_WEBHOOK_SECRET)
      .update(req.body).digest("hex")
    if (sig !== expected) return res.status(401).end()
    const e = JSON.parse(req.body.toString("utf-8"))
    // dispatch on e.event_type
    res.json({ received: true })
  })

Полные payload-схемы и политика повторов — Каталог webhook-событий.

Грабли

Что ломалось у тех, кто интегрировался раньше. Эти же правила «вшиты» в Claude-агента.

🔴 Критичные

  • recca_ref не доходит — дерево пустое. Сохраняйте ?recca_ref= в cookie и кладите в upsert.
  • upsert без leads.createkpi.leads_count=0, Recent Leads пусты. Зовите оба, external_id=signup:<id>.
  • landing_url_template без имени — голый ?{ref} ломает URLSearchParams.get('recca_ref'). Только ?recca_ref={ref}.
  • Чтение мимо обёрткиtenant_recca_id лежит в body.data, не в корне.
  • full_name вместо first_name — 400. Поле называется first_name.

🟡 Важные

  • Телефон не E.164 — только +79991234567, без скобок/пробелов.
  • Неверифицированный email не матчится — нужен email_verified: true.
  • tenant_recca_id не сохранён — без него нет impersonate-вызовов.
  • cookie recca_ref не очищен — следующий юзер привяжется к чужому рефереру.
  • claim_required: true — внутренний флаг, игнорируйте.

🟢 Мелкие

  • 403 на /embed/* — origin не в allowed_origins.
  • HMAC не сходится — нужен express.raw(), не express.json().
  • Лишний Recca-попап — переключитесь на Flow B.
  • VKID падает на Safari — добавьте GET-handler на VKID callback (ITP redirect-fallback).
  • Vue ругается на recca-тегиisCustomElement в конфиге.
  • нет short_url — поднимите @recca/federation-sdk до 1.2.0+.
🚫 Никогда: не отзывайте боевой rtk_live_… «на всякий», не меняйте tenant_recca_id, не удаляйте federation_user_membership, не хардкодьте recca_ref, не светите ключ в браузере, не выключайте HMAC.