API и вебхуки QRVC
Автоматизируйте работу с визитками: создавайте их из своих систем, забирайте контакты в CRM и получайте события в реальном времени.
Авторизация
Создайте ключ в личном кабинете: Интеграции → API-ключи. Передавайте его в заголовке каждого запроса. Ключ даёт доступ только к вашим визиткам — храните его как пароль.
curl https://www.qrvc.ru/api/v1/me \ -H "Authorization: Bearer qrvc_ваш_ключ"
- Формат — JSON, кодировка UTF-8, даты — ISO 8601 (UTC).
- Лимит — 120 запросов в минуту на ключ. При превышении —
429и заголовокRetry-After. - Ошибки:
{ "statusCode": 400, "statusMessage": "описание" }. Коды: 400 — неверные данные, 401 — ключ, 404 — не найдено, 409 — alias занят.
Визитки
GET/api/v1/me
Информация об аккаунте владельца ключа.
GET/api/v1/cards
Все ваши визитки.
POST/api/v1/cards
Создать визитку. Ответ — 201 и созданная визитка со ссылкой url.
GET/api/v1/cards/{id}
Одна визитка. Вместо id можно передать alias.
PATCH/api/v1/cards/{id}
Изменить поля визитки. QR-код и ссылка при этом не меняются.
DELETE/api/v1/cards/{id}
Удалить визитку вместе со статистикой и контактами. Ответ — 204.
Пример: создать визитку
curl -X POST https://www.qrvc.ru/api/v1/cards \
-H "Authorization: Bearer qrvc_ваш_ключ" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Иван",
"lastName": "Петров",
"phone": "+79991234567",
"email": "ivan@company.ru",
"company": "ООО Ромашка",
"position": "Менеджер по продажам",
"alias": "ivan-petrov"
}'Ответ
{
"data": {
"id": "cm1abc…",
"alias": "ivan-petrov",
"url": "https://www.qrvc.ru/card/ivan-petrov",
"firstName": "Иван",
"lastName": "Петров",
"phone": "+79991234567",
"email": "ivan@company.ru",
"theme": "classic",
…
}
}Поля визитки
| firstName* | Имя |
| lastName* | Фамилия |
| phone* | Телефон |
| email* | |
| middleName | Отчество |
| position | Должность |
| company | Компания |
| website, vk, max, youtube, rutube | Ссылки (http/https; без схемы — добавим https://) |
| telegram | Имя пользователя Telegram или ссылка |
| alias | Короткий адрес: латиница, цифры, «-», «_», 3–64 символа |
| theme | classic, dark, purple, ocean, sunset; с премиумом также midnight, forest, rose, gold |
| accentColor | Цвет кнопок #rrggbb (премиум) |
| avatarShape | circle или rounded (премиум) |
* — обязательно при создании. В PATCH передавайте только поля, которые нужно изменить; null очищает поле.
Контакты и просмотры
GET/api/v1/contacts?since=&limit=&cardId=
Контакты, оставленные на ваших визитках. cardId — только по одной визитке.
GET/api/v1/cards/{id}/views?since=&limit=
Просмотры визитки: дата, метка размещения QR (source), устройство.
Синхронизация с CRM
Списки отдаются по возрастанию даты. Запомните nextSince из ответа и передайте его в следующем запросе — получите только новые записи. Пока hasMore: true, запрашивайте дальше.
GET /api/v1/contacts?since=2026-10-01T09:00:00.000Z&limit=100
{
"data": [
{
"id": "cm2…",
"createdAt": "2026-10-01T09:15:42.120Z",
"name": "Мария Иванова",
"phone": "+79165551234",
"email": "maria@example.ru",
"company": null,
"message": "Познакомились на выставке",
"isRegistered": false,
"card": { "id": "cm1…", "alias": "ivan-petrov", "name": "Иван Петров" }
}
],
"hasMore": false,
"nextSince": "2026-10-01T09:15:42.120Z"
}Вебхуки
Добавьте адрес в кабинете (Интеграции → Вебхуки) и выберите события. Мы отправим POST с JSON сразу после события. Подходит для Albato, n8n, Make и собственных серверов.
События
contact.created— посетитель оставил контакт через «Обменяться контактами».card.viewed— визитку открыли (повторные просмотры одного человека в течение 5 минут не считаются).ping— тестовое событие из кабинета.
Пример запроса
POST https://ваш-адрес
Content-Type: application/json
X-QRVC-Event: contact.created
X-QRVC-Delivery: cm3…
X-QRVC-Signature: t=1759312542,v1=5f2b…
{
"id": "cm3…",
"event": "contact.created",
"createdAt": "2026-10-01T09:15:42.200Z",
"data": {
"contact": { "id": "cm2…", "name": "Мария Иванова", "phone": "+79165551234", "email": null, … },
"card": { "id": "cm1…", "alias": "ivan-petrov", "name": "Иван Петров" }
}
}Плоский формат для no-code
В contact.created есть блок data.flat — все значения на первом уровне, их удобно сопоставлять с полями CRM в Albato, n8n или Make: name, first_name, last_name, phone, email, company, message, answers, card_name, card_id, utm_source, utm_medium, utm_campaign, utm_content, utm_term, qr_source, yclid, gclid, first_utm_source, first_utm_campaign, created_at. Пустые значения — пустая строка.
Для Битрикс24 и amoCRM есть прямая интеграция без вебхуков — см. Интеграции.
Дополнительные поля
Появляются в событиях, /api/v1/contacts и CSV, если на сайте включены соответствующие возможности:
contact.answers— ответы на свои поля формы:[{ "id", "label", "value" }].contact.attribution— откуда пришёл человек:{ "first": касание, "last": касание }, где касание —{ "at", "source", "utm": { "source", "medium", "campaign", "content", "term" }, "clickId", "clickIdType" }.first— первое открытие визитки с меткой,last— последнее перед заявкой.clickIdType:yclid(Яндекс Директ) илиgclid(Google Ads) — для загрузки офлайн-конверсий.view.trackingвcard.viewedи/views— метки конкретного просмотра.
Метки передаются в адресе визитки: https://www.qrvc.ru/card/ivan-petrov?utm_source=expo&utm_campaign=autumn.
Доставка и повторы
- Успех — любой ответ
2xxза 10 секунд. Перенаправления не выполняются. - При ошибке повторяем через 1, 5, 30, 120 и 360 минут. Журнал отправок — в кабинете.
- После 20 неудач подряд вебхук выключается — включите его снова в кабинете.
- Одно событие может прийти дважды — используйте
idдля защиты от дублей.
Проверка подписи
Подпись — HMAC-SHA256 от строки <t>.<тело запроса> с секретом вебхука (whsec_…). Сверяйте её, чтобы убедиться, что запрос от нас, и отбрасывайте запросы старше 5 минут.
Node.js
import crypto from 'node:crypto'
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')))
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
}Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
signed = f'{parts["t"]}.'.encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])