Подготавливаем страницу…
Подготавливаем страницу…
Инструкция для разработчика. Одна дополнительная отправка после успешной отправки формы — и согласие участника попадает в реестр акции с датой, источником и редакцией текста.
Промо-сайт остаётся вашим: платформа не заменяет форму, не хранит её и не вмешивается в вёрстку. После того как форма успешно отправлена вашим обработчиком, сайт делает ещё один запрос — к нам. Мы записываем, кто согласился, когда, по какой форме и какой текст согласия действовал в тот момент.
Отправлять согласие нужно только после успеха вашей отправки. Если форма не ушла, согласия нет: расхождение «в CRM участников больше, чем в реестре» разбирать потом нечем.
Всё перечисленное организатор находит в кабинете 1ОПД, в карточке акции и её форм. Без этих пяти пунктов подключение не начинается.
https://v2.1opd.ru.sk_… для отправки с сервера или pk_… для отправки из браузера.form_id) для каждой формы акции. Их обычно две: участие и рекламные сообщения.Имена полей не угадывайте. В примере стоит name, а форма акции может быть заведена с first_name — скопированный код ответит 400 Unknown field. Точный список отдаёт сам приём.
curl -X POST https://v2.1opd.ru/api/v2/get-fields \
-H 'API-KEY: sk_ключ_этой_формы' \
-H 'Content-Type: application/json' \
-d '{ "form_id": 12 }'
# [{"field_name":"email","field_title":"Электронная почта"},
# {"field_name":"name","field_title":"Имя"}]Этот запрос работает с ключом, выпущенным на одну форму. Ключ, покрывающий все формы сайта, для чтения полей не подойдёт — состав полей у форм разный, и выбрать за вас платформа не может.
Ключи отличаются не правами, а тем, где им можно находиться.
form_id в теле запроса. Отдельный ключ на каждую форму заводить не нужно.Один эндпоинт: POST /api/v2/create-agreement. Ключ — в заголовке API-KEY, тело — JSON.
curl -X POST https://v2.1opd.ru/api/v2/create-agreement \
-H 'API-KEY: sk_ваш_серверный_ключ' \
-H 'Content-Type: application/json' \
-d '{
"form_id": 12,
"hash_field": "participant@example.com",
"consent_given": true,
"fields": [
{ "field": "email", "value": "participant@example.com" },
{ "field": "name", "value": "Иван Петров" },
{ "field": "phone", "value": "+79991234567" }
]
}'{ field, value }. Имена — из состава формы. Лишнее поле даёт 400: это защита от опечаток, из-за которых данные молча теряются.true — отметка была, так и запишем. false — приём откажет: согласие без отметки не записывается. Не передадите — запись сохранится, но в ней останется «неизвестно», и предъявить отметку будет нечем.// Серверный обработчик промо-сайта: сначала своя механика,
// потом отправка согласия. Ключ живёт в переменных окружения.
const OPD_URL = "https://v2.1opd.ru/api/v2/create-agreement";
async function sendConsent({ formId, email, name, phone }) {
const res = await fetch(OPD_URL, {
method: "POST",
headers: {
"API-KEY": process.env.OPD_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
form_id: formId,
hash_field: email,
consent_given: true,
fields: [
{ field: "email", value: email },
{ field: "name", value: name },
{ field: "phone", value: phone },
],
}),
});
if (res.status === 200 || res.status === 202) return res.json();
if (res.status === 503) throw new RetryableError(await res.text());
// 4xx — дело в самом запросе: ключ, форма, состав полей, сроки акции.
// Повтор того же тела не поможет, поэтому пишем в лог и разбираемся.
throw new Error(`1ОПД ${res.status}: ${await res.text()}`);
}HTTP/1.1 200 OK
{
"hash": "0622845210A8C5136424513435FA90EB",
"hash_field": "participant@example.com",
"fields": [
{ "field": "email", "value": "participant@example.com" },
{ "field": "name", "value": "Иван Петров" }
]
}hash — идентификатор согласия, 32 шестнадцатеричных символа. Сохраните его: по нему потом читают запись и обрабатывают запрос участника.
HTTP/1.1 202 Accepted
{
"status": "queued",
"idempotency_key": "d4e5f6a7b8c9d0e1...",
"check_url": "/spool/status/d4e5f6a7b8c9d0e1...",
"original_path": "/api/v2/create-agreement"
}После 202 запрос не повторяют. Так мы отвечаем, когда база временно недоступна: согласие уже принято и запишется само, повторы идут ступенями до 48 часов. Повторная отправка того же тела создаст вторую версию согласия — прежняя станет неактуальной, и в реестре будут две записи об одном событии.
hash в ответе 202 нет, и посчитать его у себя нельзя: в него входит номер версии, то есть состояние на нашей стороне. Когда согласие доедет, идентификатор появится по адресу из check_url.
| Код | Что произошло | Что делать |
|---|---|---|
| 200 | Записано | Ничего. В теле — hash |
| 202 | Принято в очередь | Ничего. Не повторять |
| 400 | Нет обязательного поля, лишнее поле или consent_given: false | Исправить запрос. Повтор того же тела не поможет |
| 401 | Ключ не принят: неизвестен, отозван, истёк или не сошлась подпись | Проверить заголовок API-KEY целиком, без пробелов и переносов |
| 403 | Отклонено по домену, IP-адресу или составу форм ключа | Проверить ограничения ключа в кабинете |
| 409 | Текст согласия формы — черновик, либо к форме вообще не привязан текст | Организатор утверждает редакцию в кабинете. До этого согласия не принимаются |
| 410 | promo_finished — акция завершена | Прекратить отправку. Если срок продлён, организатор меняет даты акции |
| 503 | Недоступны и приём, и очередь | Единственный случай, когда отправку повторяют |
Участие в акции и согласие на рекламные сообщения — разные основания и разные тексты. В платформе это две формы с разными form_id, и в реестре видно, на что именно согласился человек.
На странице это две отдельные галочки. Отмечена только первая — отправляете одно согласие по форме участия. Отмечены обе — два запроса, каждый со своим form_id и своим consent_given: true. Отправлять рекламное согласие «заодно», потому что человек участвует в акции, нельзя.
Организатор может включить обязательную подпись — тогда к каждому запросу добавляются два заголовка. Допустимое расхождение часов — 5 минут.
// Подпись включается по требованию организатора.
// Секрет выдаётся один раз в кабинете, в base64.
const ts = Math.floor(Date.now() / 1000).toString();
const body = JSON.stringify(payload); // подписываем ровно то тело, что уйдёт
const sign = crypto
.createHmac("sha256", Buffer.from(process.env.OPD_HMAC_SECRET, "base64"))
.update(ts + "\n" + body)
.digest("hex"); // hex, не base64
const headers = {
"API-KEY": process.env.OPD_API_KEY,
"Content-Type": "application/json",
"API-TIMESTAMP": ts,
"API-SIGN": sign,
};Подпись передаётся в шестнадцатеричном виде, не в base64 — в base64 приходит сам секрет. Подписывается ровно то тело, которое уходит, до любых переформатирований JSON.
200 и hash в теле означают, что путь до платформы работает.Третий шаг пропускают чаще всего. Успешный ответ сервера подтверждает отправку, а не запись: ключ может писать в другую форму. Первое согласие с боевого сайта проверяют глазами всегда.
После даты окончания акции приём новых согласий закрывается: платформа отвечает 410 с признаком promo_finished и датой завершения. Ранее принятые записи остаются. Предусмотрите этот код в обработчике — иначе форма на забытой странице будет молча терять данные участников.
Если промо-сайт умеет отвечать на запросы участников, для этого есть отдельные эндпоинты: POST /api/v2/get-user отдаёт данные по hash согласия, POST /api/v2/delete-user прекращает обработку. Оба работают ключом, выпущенным на одну форму. Отдельная интеграция не обязательна: у оператора есть публичная форма обращений, её адрес выдаёт кабинет.
Платформа принимает согласия и хранит доказательства. Механика акции, проверка чеков, выбор победителей и выдача призов в промо-модуль не входят и через этот API не проходят.
За сайтом остаётся и то, что участник видит: галочка не отмечена заранее, рядом с ней стоят ссылки на политику и текст согласия, отправка согласия идёт после успешной отправки формы. Запись в реестре подтверждает факт и содержание согласия — она не исправляет форму, собравшую больше данных, чем покрывает её текст.
60 000 ₽ за акцию до трёх месяцев. Каждый дополнительный месяц стоит 6 000 ₽. После завершения доступ к данным бесплатен три года.