HMAC visitor token — примеры на популярных стэках
Готовые сниппеты эндпоинта /api/supporthub/token для FastAPI, Django, Flask, Express, Next.js API, Laravel, Rails, Go, .NET и Spring Boot. Полный референс с форматом payload, проверками на бэкенде и крайними случаями — на странице HMAC visitor token.
Что нужно сделать
- Получить секрет проекта один раз — скопировать в НастройкиКаналыWeb Widget (нужны права администратора) или запросить с API-ключом из НастройкиAPI ключи — и положить в env вашего бэкенда:http
GET /api/v1/widget/signing-secret Authorization: Bearer sk_xxx 200 OK { "secret": "случайная строка из 43 символов" } - Реализовать у себя эндпоинт
GET /api/supporthub/tokenпо одному из шаблонов ниже. Эндпоинт должен:- аутентифицировать текущего пользователя (через session-cookie / JWT / что угодно);
- взять стабильный
user_id(PK из вашей БД, slug, и т.д.); - собрать payload
{"user_id": "...", "exp": <unix>}; - подписать raw JSON-байты через HMAC-SHA256;
- вернуть
{ "token": "<base64url(payload)>.<hex(sig)>" }.
- Раскомментировать
fetch-блок в установочном сниппете и убрать строкуloadWidget();под ним — иначе виджет сначала загрузится без токена, а вторая загрузка, с токеном, будет проигнорирована. Загрузчик положит токен вdata-visitor-token, и виджет будет передавать его SupportHub с каждым запросом. - При выходе пользователя из аккаунта вызывать
window.SupportHub.logout()— виджет забудет токен и начнёт с чистой анонимной сессии, и следующий человек за этим браузером не увидит чужую переписку (подробнее — Identify посетителей).
Примеры реализации
Все примеры используют HMAC-SHA256 поверх raw JSON-байт (не base64-строки), base64url-кодирование payload без padding-знака =, и компактный JSON без пробелов (separators=(",", ":") или эквивалент). Бэкенд не требует ни компактного JSON, ни отсутствия = — он проверяет подпись ровно над теми байтами, что закодированы в токене. TTL — 1 час; делайте по вкусу, но не вечно.
import base64, hashlib, hmac, json, os, time
from fastapi import APIRouter, Depends, HTTPException
router = APIRouter()
SECRET = os.environ["SUPPORTHUB_SIGNING_SECRET"].encode()
@router.get("/api/supporthub/token")
async def supporthub_token(current_user = Depends(get_current_user)):
if not current_user:
raise HTTPException(401, "Unauthorized")
payload = json.dumps(
{"user_id": str(current_user.id), "exp": int(time.time()) + 3600},
separators=(",", ":"),
).encode()
sig = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()
payload_b64 = base64.urlsafe_b64encode(payload).rstrip(b"=").decode()
return {"token": f"{payload_b64}.{sig}", "expires_in": 3600}Тестовый прогон без бэкенда
Если хотите подёргать API руками до прикручивания эндпоинта — подпишите payload, например, в Python REPL и положите в HTML атрибутом data-visitor-token:
import base64, hashlib, hmac, json, time
SECRET = "PASTE_SECRET_HERE" # секрет проекта
payload = json.dumps(
{"user_id": "demo-42", "exp": int(time.time()) + 3600},
separators=(",", ":"),
).encode()
sig = hmac.new(SECRET.encode(), payload, hashlib.sha256).hexdigest()
print(base64.urlsafe_b64encode(payload).rstrip(b"=").decode() + "." + sig)<script async
src="https://support.forestsnet.com/widget-bundle?ws=YOUR_WS"
data-visitor-token="ВЫВОД_ИЗ_PYTHON_ВЫШЕ"></script>Дальше
- Полный референс HMAC — формат payload, что проверяет бэкенд SupportHub и какие 401 возвращает, крайние случаи (просрочка токена, вход посреди сессии, выход и смена аккаунта, несколько вкладок), смена секрета, как проверить токен curl'ом.
- Identify посетителей — что передавать в
data-*рядом с токеном (email, имя, телефон, Telegram ID). - Troubleshooting — типичные проблемы виджета, в том числе токен, который бэкенд не принимает (401).

