Подготавливаем страницу…
Подготавливаем страницу…
REST-эндпоинты, аутентификация, HMAC, Origin/IP whitelist, multi-form через forms_scope, коды ошибок.
https://app.1opd.ru/api/v2get-user, см. ниже).API-KEY.API-SIGN и API-TIMESTAMP (HMAC).ok: true/false. В случае ошибки добавляется reason (на русском).Создать ключ для своего сайта → ваш ЛК 1ОПД .
Самая частая ошибка интеграторов — выпустить ключ без защиты или выбрать защиту, неподходящую под клиента (например, IP whitelist для Tilda — у посетителей разные IP). Сверьтесь с таблицей:
| Тип клиента | Ключ | Защита | Гайд |
|---|---|---|---|
| Браузерный JS — Tilda, лендинги, статичный HTML | pk_ | Origin whitelist (allowed_origins) | Открыть → |
| SPA — React / Vue / Next.js client-side | pk_ | Origin whitelist + wildcard *.domain.ru на поддомены | Открыть → |
| Server-side на static IP — VPS / bare-metal (PHP, Node, Python, WordPress hook) | sk_ | IP whitelist | Открыть → |
| Контейнеры / serverless — Docker, k8s, Cloud Run, Lambda | sk_ | HMAC-подпись | Открыть → |
| Bitrix CMS / Bitrix24 CRM (PHP-hook) | sk_ | IP whitelist (опционально + HMAC) | Открыть → |
| Параноидальный режим — фиксированный IP + защита от утечки ключа | sk_ | IP whitelist + HMAC одновременно | Открыть → |
Все запросы должны содержать заголовок API-KEY с вашим ключом из ЛК 1ОПД.
Безопасно публиковать в HTML/JS. Защита через allowed_origins. Для Tilda, WordPress JS, статичных сайтов, SPA.
Только server-side. IP whitelist или HMAC. Для PHP / Node.js / Python / Bitrix / WordPress hook.
Основной endpoint: создаёт запись согласия. Доступен для pk_ и sk_ (требует право create).
form_id (number) — ID формы. Опционально для single-form ключей, обязательно при forms_scope.hash_field (string, required) — значение поля-идентификатора (обычно email или phone).fields (array of {field, value}, required) — все поля формы. POST /api/v2/create-agreement HTTP/1.1
Host: app.1opd.ru
API-KEY: pk_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"form_id": 1,
"hash_field": "user@example.com",
"fields": [
{ "field": "email", "value": "user@example.com" },
{ "field": "name", "value": "Иван Иванов" },
{ "field": "phone", "value": "+79991234567" }
]
} POST /api/v2/create-agreement HTTP/1.1
Host: app.1opd.ru
API-KEY: pk_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
# Один ключ с forms_scope=[1,2,5] обслуживает все формы сайта.
# form_id в body выбирает конкретную форму:
{
"form_id": 2,
"hash_field": "user@example.com",
"fields": [
{ "field": "email", "value": "user@example.com" }
]
} HTTP/1.1 200 OK
Content-Type: application/json
{
"hash": "0622845210A8C5136424513435FA90EB",
"hash_field": "user@example.com",
"fields": [
{ "field": "email", "value": "user@example.com" },
{ "field": "name", "value": "Иван Иванов" }
]
}
# hash — идентификатор согласия (32 hex-символа, верхний регистр).
# Сохраните его: по нему потом читают (get-user) и отзывают (delete-user).
# Если бэкенд временно недоступен, ответ будет 202 Accepted —
# согласие принято в очередь и запишется автоматически при восстановлении:
# HTTP/1.1 202 Accepted
# {
# "status": "queued",
# "idempotency_key": "d4e5f6a7b8c9d0e1...",
# "message": "Accepted for processing. Will be delivered when backend recovers.",
# "check_url": "/spool/status/d4e5f6a7b8c9d0e1...",
# "original_path": "/api/v2/create-agreement"
# }
#
# Это НЕ ошибка — считайте 202 успехом и не ретрайте: повтор с тем же телом
# дедуплицируется по idempotency_key, дубля согласия не будет.
#
# ⚠ В ответе 202 НЕТ поля hash — и посчитать его у себя нельзя.
# hash — это идентификатор СОГЛАСИЯ, а не субъекта:
# hash = MD5("<form_id> <MD5(hash_field)> <версия согласия>")
# Версия — сколько раз этот субъект уже соглашался в этой форме, то есть
# состояние на нашей стороне. Поэтому дождаться его придётся от нас.
#
# Как получить hash после 202: по идентификатору из ответа
# GET /spool/status/<idempotency_key>
# После доставки там появится поле delivered_hash.
#
# ⛔ НЕ отправляйте тот же запрос повторно, чтобы «получить hash в 200».
# Повтор не идемпотентен на уровне согласия: он пометит прежнюю запись
# неактуальной и создаст НОВУЮ версию согласия, а оригинал всё равно доедет
# из очереди. Получите две версии одного согласия вместо одной.
#
# Сколько мы держим очередь: повторы идут ступенями от момента приёма —
# 10 мин, 30 мин, 1 ч, 3 ч, 6 ч, 12 ч, сутки, 36 ч, 48 ч. Плюс запись
# разбирается сразу, как только бэкенд снова отвечает, не дожидаясь ступени.
# Само тело хранится 72 часа. Если за это время доставить не удалось —
# мы это видим по своим счётчикам, но вам уведомление не приходит.
#
# check_url — если захотите убедиться, что отложенное согласие дошло:
# GET https://app.1opd.ru/spool/status/<idempotency_key>
# Опрашивать не обязательно — доставка на нас; это на случай разбирательства.
#
# Возможные status:
# waiting_retry — ждёт следующей попытки, время в next_attempt_at
# replaying — отправляется прямо сейчас
# delivered — записано, время в delivered_at
# failed_permanent — бэкенд ответил 4xx: дело в самом запросе (ключ, форма,
# обязательное поле). Причина — в last_error, повтор
# того же тела не поможет
# failed_max_attempts — девять попыток за 48 часов не прошли
#
# 404 — записи нет: либо ключа не существовало, либо прошло больше 72 часов
# и она вышла из хранения. Тело и заголовки запроса эндпоинт не отдаёт. Получить схему формы. Полезно для динамической генерации форм или валидации перед отправкой.
POST /api/v2/get-fields HTTP/1.1
Host: app.1opd.ru
API-KEY: pk_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{ "form_id": 1 } HTTP/1.1 200 OK
Content-Type: application/json
# Ответ — массив полей, объявленных у этой формы (без обёртки):
[
{ "field_name": "email", "field_title": "Электронная почта" },
{ "field_name": "first_name", "field_title": "Имя" },
{ "field_name": "phone_number", "field_title": "Номер телефона" }
]
# field_name — то, что вы передаёте в fields[].field при create-agreement.
# Полный перечень возможных полей — в таблице ниже на этой странице. Получить запись по hash. Требует право read. Параметры — в query-string (совместимость с v1).
POST /api/v2/get-user HTTP/1.1
Host: app.1opd.ru
API-KEY: sk_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{ "hash": "0622845210A8C5136424513435FA90EB" }
# Ответ — массив пар поле→значение (без обёртки):
[
{ "field": "email", "value": "user@example.com" },
{ "field": "first_name", "value": "Иван" }
]
# Если согласие отозвано / истекло — 200 с пояснением вместо данных:
# { "status": "По данному hash был пользователь, но он был удалён. Данные недоступны." }
# Если hash не найден — 404: { "reason": "Entity not found" } Удалить запись по hash. Требует право delete. Для обработки запросов субъектов ПДн на удаление (статья 21 ФЗ-152).
POST /api/v2/delete-user HTTP/1.1
Host: app.1opd.ru
API-KEY: sk_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{ "hash": "0622845210A8C5136424513435FA90EB" }
# Ответ:
{ "status": 200 }
# Если субъект уже был удалён ранее — 200 с пояснением:
# { "status": "По данному hash был пользователь, но он был удалён. Данные недоступны." }
# Если hash не найден — 404: { "reason": "Entity not found" } Опциональный механизм для sk_. Защищает от компрометации ключа и replay-атак.
# Псевдокод (Python):
ts = str(int(time.time())) # unix timestamp, секунды
body_bytes = json.dumps(payload).encode() # тело как байты — ровно то, что отправляете
secret = base64.b64decode(secret_b64) # секрет из ЛК — base64 → bytes
# Подпись — HEX (не base64!):
sign = hmac.new(
secret, ts.encode() + b"\n" + body_bytes, hashlib.sha256
).hexdigest()
# Заголовки:
# API-KEY: sk_xxx
# API-SIGN: <hex-строка, 64 символа>
# API-TIMESTAMP: <ts> ts + "\n" + body_bytes.base64_decode(secret_b64) — секрет хранится в base64, в HMAC подаётся как bytes.hex (нижний регистр, 64 символа) — результат .hexdigest(). Не base64.API-SIGN, API-TIMESTAMP. Для pk_. Если массив allowed_origins не пуст — проверяется заголовок Origin (или host из Referer).
https:// и порта. Например: example.com, www.example.com.*.example.com подходит для shop.example.com, но не для самого example.com.Для sk_. Список IPv4/IPv6 адресов или CIDR-сетей, с которых разрешён вызов.
curl ifconfig.me (исходящий, не входящий).10.0.0.0/24, 2001:db8::/32.Self-service в ЛК: рядом с каждым ключом есть кнопка «IP-whitelist». Открываете окно, вписываете IP/CIDR (по строке), подтверждаете действие своим паролем — изменение применяется мгновенно, без обращения к менеджеру 1ОПД.
У ключа можно указать forms_scope — JSON-массив form_id, с которыми он работает. Дефолтная и рекомендуемая модель для сайтов с несколькими формами:
// Ключ pk_abc с forms_scope: [1, 2, 5]
// Запрос с form_id=1 — OK
// Запрос с form_id=2 — OK
// Запрос с form_id=3 — 403 "Форма не разрешена ключом" Если forms_scope пуст — ключ single-form: form_id в body не обязателен (берётся из метаданных ключа). Если задан — form_id в body обязателен.
В случае ошибки ответ имеет вид (обёртки нет — только reason):
{ "reason": "Неверный API-ключ" } В таблице — только ошибки самого запроса. 202 и 503 ошибками не являются и разобраны ниже, в блоке «Что вернёт API при отправке согласия».
| HTTP | Reason | Когда возникает |
|---|---|---|
| 401 | Неверный API-ключ | Ключа нет в базе или он отозван. |
| 401 | Подпись не совпадает | HMAC-проверка не прошла. Частая причина — подпись закодирована в base64: нужен hex (.hexdigest()). Также проверьте, что байты body для подписи и для отправки идентичны. |
| 401 | Timestamp устарел | Разница между API-TIMESTAMP и серверным временем больше 300 секунд. |
| 403 | Origin не в whitelist | Заголовок Origin (или хост Referer) не совпадает ни с одним из allowed_origins ключа. |
| 403 | IP не в whitelist | Источник запроса не входит в IP-whitelist ключа (для server-side). |
| 403 | Форма не разрешена ключом | У ключа задан forms_scope, и form_id из body не входит в этот список. |
| 400 | Не указано обязательное поле hash_field | Поле hash_field отсутствует или пустое. |
| 400 | Отсутствует обязательное поле формы | В fields нет одного из полей, помеченных required в схеме формы. |
| 400 | Невалидный JSON | Тело запроса не парсится как JSON. |
| 404 | Запись не найдена | Для get-user / delete-user — по hash ничего нет. |
| 429 | Превышен rate-limit | Слишком много запросов с одного ключа. Подождите и повторите. |
Самый быстрый способ убедиться, что ключ работает — отправить запрос на create-agreement с тестовыми данными. В ЛК сразу появится новая запись. Для pk_ ключа вручную добавьте заголовок Origin с одним из разрешённых доменов — Postman сам его не подставляет.
Вопросы по API, кастомные права на ключе, кастомные интеграции — пишите на info@1opd.ru или открывайте ЛК → Интеграция — там персональные snippet'ы с уже подставленными ключами.
Успехом считаются и 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 символов.