Подготавливаем страницу…
Подготавливаем страницу…
Для одностраничных приложений: интеграция вешается на 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_ переменная будет видна только на server-side (SSR/build), но не в браузере → OPD_KEY окажется undefined и fetch упадёт..env.local не деплоится — это только для локальной разработки.Production + staging + localhost для dev'а.
В ЛК 1ОПД → ваш pk_ ключ → Origin-whitelist. Добавьте все хосты, с которых SPA будет работать:
https://app.example.com — продакшенhttps://www.example.com — landinghttps://staging.example.com — staginghttp://localhost:3000 — для dev-режима Next.js (часто 3000)http://localhost:5173 — для dev-режима Vite (часто 5173)https://*.example.com — покрывает app/dashboard/beta/etc. Но он не покрывает сам example.com — его добавляйте отдельно.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 — останавливает дефолтную отправку формы.1 хук на все формы, разные form_id на вызовах.
Если в SPA несколько форм согласия (подписка, заявка, обратная связь) — не дублируйте код. Сделайте один хук, который принимает 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 ключ.Без него — нарушение 152-ФЗ.
В JSX/template вашей формы добавьте чекбокс с ссылками на consent + privacy. Готовый 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> DevTools Network + ЛК 1ОПД.
POST /api/v2/create-agreement со статусом 200 OK.1ОПД hash: ABC123… (если не убрали console.log).Самые частые проблемы и что с ними делать. Решает 95% случаев.
.env.local?NEXT_PUBLIC_ (Next) или VITE_ (Vite) обязателен.В Console увидите конкретный Origin который был отклонён. Скопируйте, добавьте в ЛК 1ОПД → ключ → Origin-whitelist.
Для dev — обычно http://localhost:3000 или http://localhost:5173.
Не забывайте про https:// vs http:// — это разные Origin'ы.
Vercel: Project Settings → Environment Variables → добавить NEXT_PUBLIC_OPD_KEY с значением вашего pk_. Затем redeploy.
Netlify: Site Settings → Environment Variables → то же самое.
В onSubmit-handler'е обязательно e.preventDefault(). В Vue — @submit.prevent или e.preventDefault() внутри метода.
Без этого браузер сделает дефолтную отправку формы и перезагрузит страницу до того как fetch уйдёт.
Вызывайте fetch в 1ОПД только из client-side: в onSubmit-handler'е, в useEffect, или в event listener'е. Не в getServerSideProps / getStaticProps / Server Components.
Попросите менеджера выпустить отдельный pk_ для staging tenant'а. Тогда в .env.local на dev/staging будет staging-ключ, в проде — prod-ключ. Тестовые записи не засорят рабочую базу.
Откройте ЛК → «Интеграция»: персональные snippet'ы + готовый HTML-чекбокс с авто-ссылками на согласие и политику + кнопка «IP-whitelist» для каждого ключа.
Открыть ЛК →Запросите тестовый доступ — выдаём ключ под ваш домен и помогаем подключиться.
Связаться →Все endpoints, коды ошибок, форматы HMAC/Origin/IP. Таблица «какую защиту выбрать».
Открыть →