Подготавливаем страницу…
Подготавливаем страницу…
Самый контролируемый путь: согласие уходит из вашего бэкенда после успешной обработки формы. Не зависит от браузера посетителя, ключ не покидает сервер.
curl ifconfig.me с сервера) в кабинете на карточке ключа. Пока список пуст, запросы принимаются с любого адреса. При переезде сервера список надо обновить.Ключ и секрет — в переменных окружения, не в коде и не в репозитории. Секрет HMAC показывается один раз при включении; при утрате выпускается новый, и прежние подписи перестают проходить.
# .env / systemd Environment= / docker-compose environment:
OPD_KEY=sk_ваш_ключ
# при включённом HMAC дополнительно:
OPD_SECRET_B64=секрет_в_base64
# Файл с секретами — в .gitignore, не в репозиторий.Во всех примерах consent_given: true — потому что сервер отправляет согласие после собственной валидации формы, где галочка обязательна. Если ваша логика этого не гарантирует, не передавайте поле.
<?php
// Обработчик формы на вашем сервере: сначала своя логика (валидация,
// письмо, CRM), затем — согласие в 1ОПД.
$body = json_encode([
'form_id' => 1, // form_id из кабинета
'hash_field' => $_POST['email'],
'consent_given' => true, // галочка проверена валидацией формы
'fields' => [
['field' => 'email', 'value' => $_POST['email']],
['field' => 'first_name', 'value' => $_POST['name']],
['field' => 'phone_number', 'value' => $_POST['phone']],
],
], JSON_UNESCAPED_UNICODE);
$ch = curl_init('https://v2.1opd.ru/api/v2/create-agreement');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'API-KEY: ' . getenv('OPD_KEY'), // sk_… из окружения, не из кода
'Content-Type: application/json',
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
// 200 и 202 — успех; 503 — единственный повод повторить; прочее — в лог.
if ($status !== 200 && $status !== 202) {
error_log("1ОПД {$status}: {$response}");
}// Node 18+, fetch встроен.
const body = JSON.stringify({
form_id: 1,
hash_field: payload.email,
consent_given: true,
fields: [
{ field: "email", value: payload.email },
{ field: "first_name", value: payload.name },
],
});
const res = await fetch("https://v2.1opd.ru/api/v2/create-agreement", {
method: "POST",
headers: {
"API-KEY": process.env.OPD_KEY!, // sk_… из окружения
"Content-Type": "application/json",
},
body,
});
if (res.status === 200) {
const { hash } = await res.json(); // сохраните: по hash читают и отзывают
} else if (res.status === 202) {
// Принято в очередь — успех, не повторять.
} else if (res.status === 503) {
// Единственный случай для повтора.
} else {
console.error("1ОПД", res.status, await res.text());
}import os, requests
r = requests.post(
"https://v2.1opd.ru/api/v2/create-agreement",
headers={
"API-KEY": os.environ["OPD_KEY"], # sk_… из окружения
"Content-Type": "application/json",
},
json={
"form_id": 1,
"hash_field": email,
"consent_given": True,
"fields": [
{"field": "email", "value": email},
{"field": "first_name", "value": name},
],
},
timeout=10,
)
if r.status_code in (200, 202):
pass # успех; при 200 в r.json()["hash"] — идентификатор записи
elif r.status_code == 503:
... # единственный случай для повтора
else:
log.error("1ОПД %s: %s", r.status_code, r.text)Три правила, на которых ломаются: подпись в hex (не base64), секрет перед использованием декодируется из base64, подписываются ровно те байты, которые уходят в запрос — до любых переформатирований JSON.
import base64, hashlib, hmac, json, os, time
import requests
secret = base64.b64decode(os.environ["OPD_SECRET_B64"]) # секрет из кабинета
ts = str(int(time.time()))
# Подписываются и отправляются ОДНИ И ТЕ ЖЕ байты — поэтому data=, не json=.
body = json.dumps({
"form_id": 1,
"hash_field": email,
"consent_given": True,
"fields": [{"field": "email", "value": email}],
}, separators=(",", ":"))
sign = hmac.new(secret, f"{ts}\n{body}".encode(), hashlib.sha256).hexdigest()
r = requests.post(
"https://v2.1opd.ru/api/v2/create-agreement",
headers={
"API-KEY": os.environ["OPD_KEY"],
"API-SIGN": sign, # hex, 64 символа. Не base64.
"API-TIMESTAMP": ts,
},
data=body,
timeout=10,
)import crypto from "node:crypto";
const secret = Buffer.from(process.env.OPD_SECRET_B64!, "base64");
const ts = String(Math.floor(Date.now() / 1000));
const body = JSON.stringify({
form_id: 1,
hash_field: payload.email,
consent_given: true,
fields: [{ field: "email", value: payload.email }],
});
const sign = crypto
.createHmac("sha256", secret)
.update(ts + "\n" + body)
.digest("hex"); // hex, не base64
await fetch("https://v2.1opd.ru/api/v2/create-agreement", {
method: "POST",
headers: {
"API-KEY": process.env.OPD_KEY!,
"API-SIGN": sign,
"API-TIMESTAMP": ts,
"Content-Type": "application/json",
},
body, // ровно та строка, что подписана
});hash подтверждает ключ и защиту (при HMAC терминальный тест удобнее писать тем же скриптом, что и боевой код).