Партнёрам

Интеграции MyDog.ru

API-ключи, вебхуки и каталог событий: получайте записи, заказы и отзывы клиентов прямо в свою систему — с подписью HMAC-SHA256 и журналом доставки.

Щенок-маскот MyDogДокументация для партнёров

Как подключиться

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

  1. Получите API-ключ

    В кабинете /admin?tab=integrations нажмите «Создать ключ»: укажите партнёра, метку и области доступа. Секрет вида mydog_… появится один раз — скопируйте его сразу.

  2. Выберите события

    Там же добавьте адрес приёмника https://… и отметьте события из каталога ниже. Секрет подписи можно задать свой или оставить пустым — платформа сгенерирует его сама.

  3. Проверьте доставку

    Кнопка «Проверить» отправляет событие integration.test. Смотрите журнал доставки: статус, ответ партнёра, подпись. Кнопка «Отправить ожидающие» разбирает очередь.

Секрет показывается один раз. В базе хранится только хеш ключа: если секрет потерян, перевыпустите ключ — старый перестанет работать сразу, а новый придёт в том же блоке с предупреждением.

Области доступа

ОбластьНазваниеЗачем нужна
catalog:readЧтение каталогаСпециалисты, услуги, товары и места: можно строить свою витрину.
bookings:readЧтение записейЗаписи клиентов: расписание, статусы, отмены.
orders:readЧтение заказовЗаказы магазина: состав, суммы, промокоды и оплата.
webhooks:manageУправление вебхукамиСоздание адресов, тестовая отправка и повтор доставок.

Каталог событий

Событие приходит методом POST на ваш адрес с заголовками X-MyDog-Event, X-MyDog-Timestamp, X-MyDog-Signature и X-MyDog-Delivery. В теле — event, sentAt и data с полями из таблицы.

СобытиеКогда приходитЧто внутри
booking.created
Создана запись
Сразу после подтверждения записи на услугуbookingIdservicespecialistdatetimepetNamepriceuserEmailКлиент записался на услугу: услуга, специалист, дата, питомец, цена
booking.cancelled
Запись отменена
В момент отмены записи клиентом или бизнесомbookingIdreasonuserEmailКлиент или бизнес отменил запись
order.created
Создан заказ
После оформления заказа в магазинеorderIditemstotalpromoCodeuserEmailЗаказ из магазина: состав, сумма, доставка, промокод
order.paid
Заказ оплачен
После подтверждения оплаты провайдеромorderIdpaidAtamountmethodОплата подтверждена платёжным провайдером
review.created
Новый отзыв
После публикации отзыва (или сразу после модерации)reviewIdspecialistIdratingtextОтзыв о специалисте или услуге с оценкой
subscription.renewed
Подписка продлена
При продлении или смене PRO-подпискиuserEmailplanKeyperiodrenewsAtPRO-подписка клиента продлена или изменена
campaign.approved
Кампания одобрена
После модерации креатива и присвоения ERIDcampaignIdadvertisereridstartDateКреатив прошёл модерацию и получил ERID
message.sent
Сообщение отправлено
После завершения рассылки или триггерного сценарияcampaignIdchannelsentskippedПисьмо или пуш ушёл клиенту из рассылки
pet.reminder_due
Пора к ветеринару
В день, когда подходит срок прививки или обработкиpetIdpetNamedueDatekindУ питомца подходит срок прививки или обработки

Каталог отдаётся публично: GET /api/v1/integrations/events — можно обновлять список событий в своём интерфейсе без авторизации.

Проверка подписи

HMAC-SHA256 по строке '<timestamp>.<тело запроса>', заголовки X-MyDog-Event, X-MyDog-Timestamp, X-MyDog-Signature

  • Считайте HMAC от строки <X-MyDog-Timestamp>.<сырое тело запроса>: точка — разделитель, тело берётся байтами, как пришло.
  • Сравнивайте подписи за постоянное время (timingSafeEqual в Node, hmac.compare_digest в Python) — обычное === открывает тайминг-атаку.
  • Не парсите и не пересобирайте JSON до проверки подписи: порядок ключей и пробелы изменят байты, и подпись не сойдётся.
  • Отклоняйте запросы со старым timestamp (например, старше 5 минут) — так replay-атака не пройдёт.
JavaScript · Express
// Express: проверяем подпись и обрабатываем событие
import express from 'express';
import crypto from 'node:crypto';

const SECRET = process.env.MYDOG_WEBHOOK_SECRET;
const app = express();

// Важно: тело нужно именно в виде сырых байтов — от него считается подпись
app.post('/mydog/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.header('X-MyDog-Timestamp') || '';
  const signature = req.header('X-MyDog-Signature') || '';
  const expected = crypto
    .createHmac('sha256', SECRET)
    .update(`${timestamp}.`)
    .update(req.body)
    .digest('hex');

  const ok =
    expected.length === signature.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
  if (!ok) return res.status(401).json({ error: 'Подпись не совпала' });

  const body = JSON.parse(req.body.toString('utf8'));
  console.log('Событие', body.event, body.data);

  // Идемпотентность: один и тот же delivery может прийти повторно
  res.status(200).json({ ok: true });
});

app.listen(3000);
Python · FastAPI
# FastAPI: проверяем подпись и обрабатываем событие
import hashlib
import hmac
import os

from fastapi import FastAPI, Header, HTTPException, Request

SECRET = os.environ["MYDOG_WEBHOOK_SECRET"]
app = FastAPI()


@app.post("/mydog/webhooks")
async def receive(
    request: Request,
    x_mydog_timestamp: str = Header(default=""),
    x_mydog_signature: str = Header(default=""),
    x_mydog_event: str = Header(default=""),
):
    body = await request.body()  # сырые байты — именно они подписаны
    message = f"{x_mydog_timestamp}.".encode() + body
    expected = hmac.new(SECRET.encode(), message, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, x_mydog_signature):
        raise HTTPException(status_code=401, detail="Подпись не совпала")

    payload = await request.json()
    print("Событие", payload.get("event"), payload.get("data"))
    return {"ok": True}

Повторы и идемпотентность

повтор вручную из кабинета; автоматические попытки — в плане

  • Отвечайте 2xx сразу, а обработку уводите в фон: доставка ждёт ответа ограниченное время. Любой не-2xx код считается ошибкой и увеличивает счётчик сбоев адреса.
  • Повтор можно запустить вручную: кнопка «Повторить» в журнале доставки вызывает POST /api/v1/admin/webhooks/deliveries/{id}/retry. Номер попытки приходит в заголовке X-MyDog-Attempt.
  • Очередь разбирается кнопкой «Отправить ожидающие» — POST /api/v1/admin/webhooks/process отправляет все доставки в статусе «в очереди» и «ошибка» и возвращает итог.
  • Идемпотентность на вашей стороне: ключ дедупликации — пара X-MyDog-Delivery + event. Один и тот же номер доставки может прийти повторно, поэтому храните обработанные id и отвечайте 200 на дубль.
Проверка дубля и разбор очереди
# Кабинет: отправить все ожидающие доставки (нужен токен администратора)
curl -X POST 'http://localhost:8020/api/v1/admin/webhooks/process' \
  -H 'Authorization: Bearer $MYDOG_TOKEN'

Демо-приёмник /api/v1/webhooks/sink

Публичный приёмник для отладки: принимает любое событие, а если передать ?secret=… — проверяет подпись и возвращает 401, если она не сошлась. В ответе видно verified и разобранное тело запроса.

В демо-данных платформы уже есть два адреса: салон «Пушистый друг» получает записи, отмены и отзывы, а Vet Line — заказы. Оба смотрят в этот приёмник с секретами demo-salon-secret и demo-vetline-secret.

Проверка приёмника вручную
# Проверка своего приёмника: /api/v1/webhooks/sink отвечает и подтверждает подпись
curl -X POST 'http://localhost:8020/api/v1/webhooks/sink?secret=demo-salon-secret' \
  -H 'Content-Type: application/json' \
  -H 'X-MyDog-Event: booking.created' \
  -H 'X-MyDog-Timestamp: 1767225600' \
  -H 'X-MyDog-Signature: <подпись>' \
  -d '{"event":"booking.created","data":{"bookingId":1}}'

Частые вопросы

Сколько хранится журнал доставки?

В кабинете показываются последние 50 записей (параметр limit принимает до 200). Записи не удаляются автоматически: статус, ответ партнёра, текст ошибки и подпись остаются в журнале, чтобы разобрать инцидент спустя дни. Для длинного архива выгружайте журнал через API.

Что происходит, если наш сервис недоступен?

Доставка остаётся в очереди со статусом «ошибка», у адреса растёт счётчик сбоев, а в журнале появляется текст ошибки: код ответа партнёра или причину сетевого сбоя. Когда сервис поднимется, нажмите «Отправить ожидающие» — очередь уйдёт повторно. Автоматические попытки по расписанию — в плане развития.

Как отозвать ключ или выключить адрес?

Ключ отзывается кнопкой «Отозвать» в таблице API-ключей: запросы с ним сразу отклоняются. Адрес вебхука переключается кнопкой «Включить/Выключить» — выключенный адрес не получает новые доставки и не считается сбоем. Отзыв и перевыпуск требуют двойного нажатия, чтобы исключить случайный клик.

Как проверить подпись, если у нас нет сырого тела запроса?

Сырое тело нужно сохранить до парсинга: в Express это express.raw({ type: 'application/json' }), в FastAPI — await request.body() вместо await request.json(). Если фреймворк уже распарсил JSON, подпись проверить нельзя — пересобранный JSON отличается пробелами и порядком ключей.

Какой формат у поля data?

Объект с полями из каталога событий. Для booking.created это bookingId, service, specialist, date, time, petName, price и userEmail. Новые поля могут добавляться — лишние игнорируйте, отсутствующие считайте пустыми.