Подготавливаем страницу…
Подготавливаем страницу…
Для одностраничных приложений: интеграцию вешают на onSubmit компонента-формы, без глобальных слушателей.
pk_ (publishable, можно публиковать в браузере) forms_scope=[id1, id2, …] , и в каждом запросе передаёте form_id в body.Ключ создаёт менеджер 1ОПД и передаёт вам один раз. Все endpoints → API Reference
Прочитайте один раз: дальше в инструкции эти слова уже не будут пугать.
В исходниках ключ не хардкодят: он попадёт в git и потребует ротации.
В корне проекта создайте файл .env.local (Next.js) или .env (Vite) и добавьте переменную с ключом pk_:
# .env.local (Next.js) или .env (Vite)
NEXT_PUBLIC_OPD_KEY=pk_abc12345678901234567890
VITE_OPD_KEY=pk_abc12345678901234567890
# Никогда не коммитьте этот файл в git!
# Добавьте .env.local в .gitignore NEXT_PUBLIC_OPD_KEY=pk_xxx (или VITE_OPD_KEY для Vite)..env.local в .gitignore, если ещё нет.NEXT_PUBLIC_ или VITE_ переменная останется видна только на стороне сервера (SSR и сборка), но не в браузере: OPD_KEY окажется undefined, и fetch упадёт..env.local в деплой не попадает, он нужен только для локальной разработки.Прод, staging и localhost для разработки.
В ЛК 1ОПД → ключ pk_ → Origin-whitelist. Внесите все хосты, с которых работает приложение:
https://app.example.com (прод)https://www.example.com (лендинг)https://staging.example.com (staging)http://localhost:3000 (dev-режим Next.js)http://localhost:5173 (dev-режим Vite)https://*.example.com: он покроет app, dashboard, beta и прочие. Сам example.com под wildcard не попадает, вносите его отдельной строкой.onSubmit с preventDefault и fetch к 1ОПД.
Скопируйте полный пример ниже, подставьте свой form_id и приведите поля к своей форме.
// LeadForm.tsx (Next.js / Vite + React)
import { useState } from "react";
const OPD_API = "https://app.1opd.ru/api/v2/create-agreement";
const OPD_KEY = process.env.NEXT_PUBLIC_OPD_KEY!; // ← pk_xxxxxxxxx из .env
export function LeadForm() {
const [email, setEmail] = useState("");
const [name, setName] = useState("");
const [submitting, setSubmitting] = useState(false);
async function recordConsent(payload: Record<string, string>) {
try {
const res = await fetch(OPD_API, {
method: "POST",
headers: { "API-KEY": OPD_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
form_id: 1, // ← form_id из ЛК 1ОПД
hash_field: payload.email,
fields: Object.entries(payload).map(([k, v]) => ({ field: k, value: v })),
}),
});
const data = await res.json();
return data.hash as string | undefined;
} catch {
// 1ОПД временно недоступен — не блокируем основную отправку
return undefined;
}
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSubmitting(true);
await recordConsent({ email, name });
// Здесь — ваша основная отправка (POST на /api/leads, mutation, и т.д.)
setSubmitting(false);
}
return (
<form onSubmit={handleSubmit}> <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" required /> <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Имя" required /> <label> <input type="checkbox" name="opd_consent" required /> Даю согласие на обработку персональных данных в соответствии с
Политикой обработки персональных данных
</label> <button disabled={submitting}>Отправить</button> </form> );
} form_id: 1 на ваш номер из ЛК.NEXT_PUBLIC_OPD_KEY определён в .env.local.Через <script setup> и ref().
<!-- LeadForm.vue (Vue 3 + Vite) -->
<script setup lang="ts">
import { ref } from "vue";
const OPD_API = "https://app.1opd.ru/api/v2/create-agreement";
const OPD_KEY = import.meta.env.VITE_OPD_KEY; // ← pk_xxxxxxxxx из .env
const email = ref("");
const name = ref("");
async function submit() {
try {
await fetch(OPD_API, {
method: "POST",
headers: { "API-KEY": OPD_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
form_id: 1,
hash_field: email.value,
fields: [
{ field: "email", value: email.value },
{ field: "name", value: name.value },
],
}),
});
} catch (err) {
console.warn("1ОПД error:", err);
}
// ... ваша основная логика
}
</script> <template> <form @submit.prevent="submit"> <input v-model="email" placeholder="Email" required /> <input v-model="name" placeholder="Имя" required /> <label> <input type="checkbox" name="opd_consent" required /> Даю согласие на обработку персональных данных в соответствии с
Политикой обработки персональных данных
</label> <button>Отправить</button> </form>
</template> @submit.prevent в Vue делает то же, что e.preventDefault() в React: останавливает штатную отправку формы.Один хук на все формы, разные form_id в вызовах.
Когда в приложении несколько форм согласия (подписка, заявка, обратная связь), код дублировать не нужно. Сделайте один хук, принимающий form_id параметром.
// hooks/use1OPD.ts — переиспользуемый хук для multi-form
export function use1OPD() {
const API = "https://app.1opd.ru/api/v2/create-agreement";
const KEY = process.env.NEXT_PUBLIC_OPD_KEY!;
async function record(
formId: number,
fields: Record<string, string>,
hashField = "email" ) {
return fetch(API, {
method: "POST",
headers: { "API-KEY": KEY, "Content-Type": "application/json" },
body: JSON.stringify({
form_id: formId, // multi-form: разные form_id
hash_field: fields[hashField],
fields: Object.entries(fields).map(([k, v]) => ({ field: k, value: v })),
}),
}).then((r) => r.json());
}
return { record };
}
// Использование:
// const { record } = use1OPD();
// await record(1, { email: "ivan@example.com", name: "Иван" });
// await record(2, { email: "ivan@example.com", phone: "+79991234567" });
// ↑ разные form_id, тот же ключ — multi-form через forms_scope forms_scope=[1, 2, 3], то есть перечень разрешённых form_id. Запросите у менеджера multi-form ключ.Согласие субъект даёт явным действием (ст. 9 ч. 1 152-ФЗ).
В JSX или template формы добавьте чекбокс со ссылками на согласие и политику обработки ПДн. Готовый HTML лежит в ЛК 1ОПД → «Интеграция» → блок «HTML-чекбокс для формы».
Минимальный пример для React:
{/* Основной чекбокс — согласие на обработку ПДн (обязательный) */}
<label style={{display:'flex', gap:8, alignItems:'flex-start'}}> <input type="checkbox" name="opd_consent" required /> <span> Даю согласие на обработку персональных данных в соответствии с{' '}
<a href="https://app.1opd.ru/public/d/<slug>/<id>/privacy_policy" target="_blank"> Политикой обработки персональных данных
</a>.
</span>
</label> {/* Опционально: маркетинговый чекбокс (без required) */}
<label style={{display:'flex', gap:8, alignItems:'flex-start', marginTop: 8}}> <input type="checkbox" name="opd_consent_marketing" /> <span>Даю согласие на получение информационной и рекламной рассылки.</span>
</label> Вкладка Network в DevTools и раздел «Согласия» в ЛК 1ОПД.
POST /api/v2/create-agreement со статусом 200 либо 202: приём означают оба.1ОПД hash: ABC123…, если console.log остался в коде.Самые частые проблемы и что с ними делать. Решает 95% случаев.
.env.local.NEXT_PUBLIC_ (Next) или VITE_ (Vite) обязателен.Console показывает отклонённый Origin: скопируйте его в ЛК 1ОПД → ключ → Origin-whitelist.
Для разработки это обычно http://localhost:3000 или http://localhost:5173.
Учитывайте схему: https:// и http:// дают разные Origin.
Vercel: Project Settings → Environment Variables → добавьте NEXT_PUBLIC_OPD_KEY со значением своего pk_, затем redeploy.
Netlify: Site Settings → Environment Variables, порядок тот же.
В onSubmit-обработчике обязателен e.preventDefault(). В Vue это @submit.prevent либо тот же e.preventDefault() внутри метода.
Иначе браузер отправит форму штатно и перезагрузит страницу раньше, чем уйдёт fetch.
Вызывайте fetch к 1ОПД только на клиенте: в onSubmit-обработчике, в useEffect или в слушателе события. Не в getServerSideProps, getStaticProps и Server Components.
Попросите менеджера выпустить отдельный pk_ для staging-тенанта. Тогда в .env.local на dev и staging пойдёт staging-ключ, а на проде прод-ключ, и тестовые записи не попадут в рабочий реестр.
Откройте ЛК → «Интеграция»: персональные snippet'ы + готовый HTML-чекбокс с авто-ссылками на согласие и политику + кнопка «IP-whitelist» для каждого ключа.
Открыть ЛК →Запросите тестовый доступ: выдаём ключ под ваш домен и помогаем подключиться.
Связаться →Все endpoints, коды ошибок, форматы HMAC/Origin/IP. Таблица «какую защиту выбрать».
Открыть →Успехом считаются и 200, и 202. Проверка «строго 200» отметит ошибкой согласие, которое 1ОПД принял и доставит.
В массиве fields передавайте любые из 28 полей ниже, имя поля стоит в столбце field. Обязательное одно: email, он же идентификатор субъекта (hash_field). Остальные 27 опциональны, незаполненное поле не передавайте. Все значения 1ОПД шифрует на своей стороне (AES-256-GCM). Актуальный список полей конкретной формы отдаёт POST /api/v2/get-fields.
| field | Название | Примечание |
|---|---|---|
email | Электронная почта | обязательное · идентификатор (hash_field) |
first_name | Имя | опциональное |
last_name | Фамилия | опциональное |
sur_name | Отчество | опциональное |
birth_date | Дата рождения | опциональное |
phone_number | Номер телефона | опциональное |
birth_place | Место рождения | опциональное |
registration_address | Адрес регистрации | опциональное |
delivery_address | Адрес доставки | опциональное |
passport_details | Паспортные данные | опциональное |
inn | ИНН | опциональное |
snils | СНИЛС | опциональное |
bank_details | Банковские реквизиты | опциональное |
account_vk | Аккаунт VK | опциональное |
account_telegram | Аккаунт Telegram | опциональное |
account_instagram | Аккаунт Instagram | опциональное |
account_facebook | Аккаунт Facebook | опциональное |
account_viber | Аккаунт Viber | опциональное |
account_ok | Аккаунт OK | опциональное |
account_whatsapp | Аккаунт WhatsApp | опциональное |
job_title | Должность | опциональное |
organization_name | Название организации | опциональное |
field_of_activity | Сфера деятельности | опциональное |
city | Город | опциональное |
mattermost_account | Аккаунт Mattermost | опциональное |
slack_account | Аккаунт Slack | опциональное |
ms_teams_account | Аккаунт MS Teams | опциональное |
sex | Пол | значения male / female · в карточках: «мужчина» / «женщина» |
Набор одинаков для всех способов интеграции (Tilda, WordPress, Bitrix, браузерный JS, server-side API). Каждое значение приходит строкой длиной до 5000 символов.