Обзор · выбор модели
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 API →
POST /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 |
| Баланс / KPI | GET /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_ref | user + 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.create —
kpi.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.