Большое обновление QRVC: обмен контактами, статистика и новое оформлениеПопробовать

API и вебхуки QRVC

Автоматизируйте работу с визитками: создавайте их из своих систем, забирайте контакты в CRM и получайте события в реальном времени.

Авторизация

Создайте ключ в личном кабинете: Интеграции → API-ключи. Передавайте его в заголовке каждого запроса. Ключ даёт доступ только к вашим визиткам — храните его как пароль.

curl https://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://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://qrvc.ru/card/ivan-petrov",
    "firstName": "Иван",
    "lastName": "Петров",
    "phone": "+79991234567",
    "email": "ivan@company.ru",
    "theme": "classic",
    …
  }
}

Поля визитки

firstName*Имя
lastName*Фамилия
phone*Телефон
email*Email
middleNameОтчество
positionДолжность
companyКомпания
website, vk, max, youtube, rutubeСсылки (http/https; без схемы — добавим https://)
telegramИмя пользователя Telegram или ссылка
aliasКороткий адрес: латиница, цифры, «-», «_», 3–64 символа
themeclassic, dark, purple, ocean, sunset; с премиумом также midnight, forest, rose, gold
accentColorЦвет кнопок #rrggbb (премиум)
avatarShapecircle или 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://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"])