Эталонная реализация (PHP)
Отправная точка, а не обязательный код: архитектура сайта ваша. Здесь показан минимум — проверка подписи, поиск по своим данным, ответ и отправка уведомления.
dd-config.php — вне git, своё значение на каждом окружении #
<?php
const DD_SERVER_SECRET = '…серверный секрет из панели…';
const DD_NOTIFY_URL = 'https://app.direct-dialog.ru/api/sites/shop.ru/marks/changed';
Эндпоинт проверки посетителя (канал 2) #
<?php
declare(strict_types=1);
require __DIR__ . '/dd-config.php';
header('Content-Type: application/json; charset=utf-8');
function fail(int $code, string $error): never
{
http_response_code($code);
echo json_encode(['error' => $error]);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { fail(405, 'method'); }
// 1. Свежесть и подпись — до любой работы с телом
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_DD_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_DD_SIGNATURE'] ?? '';
if (!ctype_digit($ts) || abs(time() - (int)$ts) > 300) { fail(401, 'stale'); }
if (!hash_equals('sha256=' . hash_hmac('sha256', $ts . '.' . $body, DD_SERVER_SECRET), $sig)) {
fail(401, 'bad_signature');
}
$req = json_decode($body, true);
if (!is_array($req) || ($req['event'] ?? '') !== 'visitor.marks') { fail(400, 'bad_request'); }
// 2. Свои идентификаторы из ключей
$userIds = [];
foreach ($req['keys'] ?? [] as $item) {
$key = (string)($item['key'] ?? '');
if (str_starts_with($key, 'user:')) { $userIds[] = substr($key, 5); }
}
if (!$userIds) {
echo json_encode(['ttl' => 900, 'marks' => []]);
exit;
}
// 3. Решение принимает сайт. Не смогли узнать — 503, а не пустой список
try {
$until = planPaidUntil($userIds); // unix-время окончания тарифа или 0
} catch (Throwable $e) {
fail(503, 'backend_unavailable');
}
if ($until <= time()) {
echo json_encode(['ttl' => 900, 'marks' => []]);
exit;
}
// 4. Метка
echo json_encode([
'ttl' => 3600,
'marks' => [[
'key' => 'vip',
'priority' => 10,
'expires_at' => gmdate('Y-m-d\TH:i:s\Z', $until),
'tooltip' => 'Тариф «Бизнес» до ' . date('d.m.Y', $until),
'live_row_bg' => '#aaddcc6b',
'live_row_bg_dark' => '#2e7d6440',
'chat_rail_bottom' => '#2e9e7a',
'badge' => ['text' => 'Бизнес', 'color' => '#2e9e7a'],
'counter_icon' => '⭐️',
'max_prefix' => '⭐ Приоритетный',
]],
], JSON_UNESCAPED_UNICODE);
Уведомление об изменении (канал 3) — вызывать после оплаты, возврата, смены тарифа #
/**
* Сообщает «Прямому Диалогу», что у пользователей изменился статус.
* Не бросает исключений и не задерживает основной сценарий дольше 3 секунд.
*/
function ddNotifyChanged(array $userIds): void
{
$keys = [];
foreach ($userIds as $id) {
$id = trim((string)$id);
if ($id !== '') { $keys['user:' . $id] = true; }
}
if (!$keys) { return; }
$body = json_encode(['keys' => array_keys($keys)]);
$ts = (string)time();
$ch = curl_init(DD_NOTIFY_URL);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_TIMEOUT => 3,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-DD-Timestamp: ' . $ts,
'X-DD-Signature: sha256=' . hash_hmac('sha256', $ts . '.' . $body, DD_SERVER_SECRET),
],
]);
curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code !== 202) { error_log('Direct Dialog notify failed: HTTP ' . $code); }
}
Проверить уведомление вручную #
SECRET='…серверный секрет…'
TS=$(date +%s)
BODY='{"keys":["user:98765"]}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -i -X POST \
-H 'Content-Type: application/json' \
-H "X-DD-Timestamp: $TS" \
-H "X-DD-Signature: sha256=$SIG" \
--data "$BODY" \
https://app.direct-dialog.ru/api/sites/shop.ru/marks/changed
# ожидается HTTP 202