Загружаем 1ОПД
Подготавливаем страницу…
Подготавливаем страницу…
Для статичного сайта, лендинга и SPA без своего бэкенда. Публикуемый ключ pk_, защита списком доменов, запрос устроен так, чтобы браузеру не понадобился preflight.
pk_ по замыслу виден в исходниках страницы — защищает его список доменов (Origin) в кабинете. Секретный sk_ в браузерном коде — утечка, выпускайте новый.ваш-домен.ru, и www.ваш-домен.ru; шаблон *.домен.ru покрывает поддомены, но не сам домен.api_key), Content-Type: text/plain, никаких своих заголовков. Свой заголовок делает запрос «непростым»: браузер шлёт preflight OPTIONS, и любая мелочь в его согласовании молча отменяет POST — в консоли лишь «Failed to fetch». Вариант с заголовком API-KEY платформа тоже принимает, но выбирать его в браузере незачем.consent_given: true. Ошибка платформы не должна блокировать саму форму.Форма с двумя чекбоксами и отправкой по каждому отмеченному. Ссылки на политику и текст согласия под галочкой возьмите в кабинете — блок «HTML-чекбокс для формы» отдаёт готовую разметку с подставленными адресами.
<form id="lead-form">
<input name="email" type="email" required placeholder="Электронная почта" />
<input name="first_name" type="text" required placeholder="Имя" />
<!-- Основной чекбокс — обязательный. -->
<label>
<input type="checkbox" name="opd_consent" required />
Даю согласие на обработку персональных данных в соответствии с
<a href="ССЫЛКА_НА_ПОЛИТИКУ" target="_blank">Политикой обработки
персональных данных</a>.
</label>
<!-- Согласие на рассылку — добровольное, БЕЗ required. -->
<label>
<input type="checkbox" name="opd_consent_marketing" />
Даю согласие на получение информационной и рекламной рассылки.
</label>
<button type="submit">Отправить</button>
</form>
<script>
// Каждой галочке — своя форма согласия. Один ключ обслуживает обе:
// его область (forms_scope) задаётся при выпуске в кабинете.
var OPD_KEY = 'pk_ваш_ключ';
var OPD_URL = 'https://v2.1opd.ru/api/v2/create-agreement';
var FORM_IDS = { opd_consent: 1, opd_consent_marketing: 2 };
function sendConsent(formId, fields, email) {
// Запрос намеренно «простой»: ключ в теле, Content-Type text/plain,
// никаких своих заголовков — браузеру не нужен preflight OPTIONS,
// которому есть что молча отменить.
return fetch(OPD_URL, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
body: JSON.stringify({
api_key: OPD_KEY,
form_id: formId,
hash_field: email,
consent_given: true, // отправляем только за отмеченную галочку
fields: fields
})
}).then(function (r) {
// Успех — и 200, и 202. Повторять после 202 нельзя.
if (r.status !== 200 && r.status !== 202) {
r.json().then(function (d) { console.warn('1ОПД:', r.status, d.reason); });
}
}).catch(function (e) { console.warn('1ОПД: ответ не получен', e); });
}
document.getElementById('lead-form').addEventListener('submit', function (e) {
// Согласие уходит ПОСЛЕ вашей основной отправки — здесь она успешна,
// раз браузер пропустил submit с required-полями.
var fd = new FormData(e.target);
var email = String(fd.get('email') || '');
if (!email) return;
var fields = [
{ field: 'email', value: email },
{ field: 'first_name', value: String(fd.get('first_name') || '') }
];
Object.keys(FORM_IDS).forEach(function (checkbox) {
if (fd.get(checkbox)) sendConsent(FORM_IDS[checkbox], fields, email);
});
// ...дальше ваша собственная обработка формы
});
</script>Тот же запрос из обработчика формы компонента. Ключ держите в переменной окружения сборки — не ради секретности (pk_ не секрет), а чтобы стенд и прод жили с разными ключами.
// LeadForm.tsx — React / Next.js (client-side)
import { useState } from "react";
const OPD_URL = "https://v2.1opd.ru/api/v2/create-agreement";
// Публикуемый ключ живёт в env сборки. Это pk_ — его видимость не утечка,
// но env удобнее: у стенда и прода ключи разные.
const OPD_KEY = process.env.NEXT_PUBLIC_OPD_KEY!;
async function recordConsent(formId: number, fields: Record<string, string>) {
try {
const res = await fetch(OPD_URL, {
method: "POST",
headers: { "Content-Type": "text/plain;charset=UTF-8" },
body: JSON.stringify({
api_key: OPD_KEY,
form_id: formId,
hash_field: fields.email,
consent_given: true,
fields: Object.entries(fields).map(([field, value]) => ({ field, value })),
}),
});
if (res.status !== 200 && res.status !== 202) {
console.warn("1ОПД:", res.status, (await res.json()).reason);
}
} catch {
// Сеть упала — основную отправку формы это блокировать не должно.
}
}
export function LeadForm() {
const [email, setEmail] = useState("");
const [name, setName] = useState("");
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
// 1. Ваша основная отправка (POST на свой бэкенд, mutation, …).
// 2. По её успеху — согласие:
await recordConsent(1, { email, first_name: name });
}
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} required />
<input value={name} onChange={(e) => setName(e.target.value)} required />
<label>
<input type="checkbox" name="opd_consent" required />
Даю согласие на обработку персональных данных…
</label>
<button>Отправить</button>
</form>
);
}<!-- LeadForm.vue — Vue 3 -->
<script setup lang="ts">
import { ref } from "vue";
const OPD_URL = "https://v2.1opd.ru/api/v2/create-agreement";
const OPD_KEY = import.meta.env.VITE_OPD_KEY;
const email = ref("");
const name = ref("");
async function submit() {
// 1. Ваша основная отправка. 2. По успеху — согласие:
try {
await fetch(OPD_URL, {
method: "POST",
headers: { "Content-Type": "text/plain;charset=UTF-8" },
body: JSON.stringify({
api_key: OPD_KEY,
form_id: 1,
hash_field: email.value,
consent_given: true,
fields: [
{ field: "email", value: email.value },
{ field: "first_name", value: name.value },
],
}),
});
} catch (e) {
console.warn("1ОПД:", e);
}
}
</script>
<template>
<form @submit.prevent="submit">
<input v-model="email" required />
<input v-model="name" required />
<label>
<input type="checkbox" name="opd_consent" required />
Даю согласие на обработку персональных данных…
</label>
<button>Отправить</button>
</form>
</template>v2.1opd.ru с кодом 200 или 202. Код 403 — домен не в списке ключа.