Documentation

Bridge API

Public API docs

Bridge API: авторизация, webhooks и каналы

Публичный контракт для внешних систем: как отправлять сообщения, принимать входящие события, проверять подпись webhook и понимать текущую готовность Telegram, MAX, WhatsApp и VK.

В колонке Telegram на этой странице теперь учтены оба режима: telegram_qr_bridge и telegram_bot.

Обновлено: 2026-05-06

Каналы

Что уже можно подключить

Сверху оставлена короткая обзорная матрица по текущим публичным каналам. `✅` означает, что возможность уже работает в текущем публичном contract, `❗` - работает частично или без отдельной законченной семантики, `❌` - в unified API/webhook пока не вынесена.

ВозможностьTGMAXVKWA
Connect: bot token
Connect: community token
Connect: phone link code
Webhook: text
Webhook: photo / image
Webhook: audio
Webhook: voice
Webhook: sticker
Webhook: animation / GIF
Webhook: document / file
Webhook: contact / share
Webhook: location / venue
Webhook: poll / dice
Webhook: sender/chat metadata
API: text
API: media (photo / video / file)
API: multiple attachments in one request
API: audio / voice / GIF / sticker
Reply context в едином contract
Forwarded marker
Deleted / revoke event
Reactions / likes
Read receipts / read status

TG = telegram_qr_bridge + telegram_bot. VK = vk_community. Для telegram_bot уже подтверждены подключение по bot token, inbound text, outbound text, photo, file, animation и multiple attachments. Для vk_community уже подтверждены connect по community token, inbound text/photo/voice/document, outbound text/photo/video/document/voice, а также multiple attachments; отдельными no пока остаются VK audio и sticker outbound.

Capability matrix

Что уже стандартизовано в webhook и API

На текущем этапе Bridge уже стандартизует входящие file-like вложения через message.inbound + attachments[] для Telegram, MAX, WhatsApp и VK. Исходящий API стандартизован вокруг POST /v1/messages и content.type = text | media.

Для reply и forwarded Bridge держит единый v1 shape прямо внутри message.inbound: поля message.reply_to и message.forwarded всегда присутствуют. Telegram и WhatsApp уже подтверждены на prod, MAX и VK сейчас остаются best-effort partial. Для reactions и read receipts Bridge публикует отдельные webhook events там, где surface уже стабилен: сейчас это WhatsApp reactions/read receipts и Telegram read best-effort.

Delete/revoke вынесен в отдельный webhook message.deleted. На текущий момент этот contract уже работает для whatsapp_qr_bridge; для Telegram, MAX и VK discovery пока не дал usable delete-signal.

Inbound: что приходит в webhook

Событие / действиеПубличный contractTelegram QRMAX QRVK CommunityWhatsApp QRПримечание
Text / emoji-only textmessage.type = text, message.textДаДаДаДаEmoji-only сообщения идут как обычный text. Для WhatsApp это отдельно проверено в prod smoke; для VK current transport это message_new из Callback API.
Photomessage.type = photo, attachments[]ДаДаДаДаФайл сохраняется в Bridge storage и отдается по signed URL. Для VK inbound photo подтверждено на prod smoke.
Videomessage.type = video, attachments[]ДаДаЧастичноДаВидео проходит через общий attachment pipeline и storage. Для VK это best-effort: Bridge использует прямые video file URLs, если VK реально отдал их в callback payload.
Video note / round videomessage.type = video_noteДаНетНетНетСейчас это отдельная нормализация только для Telegram.
Audiomessage.type = audio, attachments[]ДаДаЧастичноДаDuration и mime type заполняются best-effort. Для VK это работает там, где callback payload отдает прямой download URL.
Voicemessage.type = voice, attachments[].metadata.is_voiceДаДаДаДаВо внешний webhook голосовые идут как отдельный тип voice. Для VK это audio_message с download URL, подтвержденный на prod smoke.
Animation / GIFmessage.type = animation, attachments[]ДаДаНетДаWhatsApp animation приходит как animation и обычно хранится как video/mp4.
Stickermessage.type = sticker, attachments[]ДаДаЧастичноДаВ публичном API это единый тип sticker; отдельная семантика animated sticker пока не выделена. Для VK это current best-effort static sticker surface.
Document / filemessage.type = document, attachments[]ДаДаДаДаФайлы проходят через общий storage/download contract. Для VK inbound document подтвержден на prod smoke, включая voice/document signed URLs на публичном домене.
Contact / sharemessage.type = contact | shareЧастичноЧастичноНетНетДля Telegram это сейчас best-effort в telegram_bot; для MAX тип распознается, но общий file/media contract здесь не применяется.
Location / venuemessage.type = locationЧастичноНетНетНетПока это Telegram bot best-effort normalizer без attachments[]; координаты или venue text могут прийти в message.text.
Poll / dicemessage.type = poll | diceЧастичноНетНетНетПока это Telegram bot best-effort normalizer: question/value могут прийти в message.text, но отдельной structured payload semantics еще нет.
Sender / chat metadatadata.from.*, data.chat.*ЧастичноЧастичноЧастичноЧастичноПоля вроде username, phone, avatar, title, display_name заполняются best-effort и зависят от провайдера. Для VK current MVP names/screen_name/avatar подтягиваются best-effort через users.get.
Reply / quoted contextmessage.reply_to = null | { provider_message_id }ДаЧастичноДаДаTelegram и WhatsApp уже заполняют provider_message_id best-effort. Для MAX Bridge теперь пытается извлекать reply context из raw message.link, но до отдельного prod smoke это остается best-effort partial. Для VK reply marker читается из reply_message callback payload и подтвержден на prod smoke.
Forwarded markermessage.forwarded = null | { provider_message_id, from, message? }ДаЧастичноДаДаTelegram уже отдает marker пересылки с source metadata. В WhatsApp это best-effort extraction из contextInfo и raw message wrapper; если provider скрывает source metadata, marker все равно приходит как object с null-полями. Для MAX Bridge это best-effort extraction из raw message.link, включая попытку поднять text-comment из linked payload; статус пока partial до отдельного smoke. Для VK marker поднимается из fwd_messages; если у пересылки есть свой комментарий, он остается в message.text, а исходный текст forwarded message приходит в optional message.forwarded.message.text, и это подтверждено на prod smoke.
Deleted / revokedmessage.deleted webhook eventНетНетНетЧастичноWhatsApp уже публикует message.deleted по revoke/protocolMessage surface. Для Telegram, MAX и VK usable delete-signal пока не подтвержден в текущем discovery.
Reactions / likesmessage.interaction webhook eventЧастичноНетНетДаBridge уже публикует WhatsApp reactions для сообщений, которые были отправлены через Bridge. Для Telegram event пока не подключен. Для MAX live discovery на 29 апреля 2026 года не показал reaction markers в текущем user-session sync stream. Для VK current MVP reaction surface пока не реализован.
Read receiptsmessage.read webhook eventЧастичноНетНетДаWhatsApp уже отдает read receipts для Bridge-sent сообщений; Telegram публикует best-effort read events по UpdateReadHistoryOutbox. Для MAX live discovery на 29 апреля 2026 года не показал read markers в текущем user-session sync stream. Для VK current MVP read surface пока не реализован.

Outbound: что принимает API

Событие / действиеПубличный contractTelegram QRMAX QRVK CommunityWhatsApp QRПримечание
TextPOST /v1/messages, content.type = textДаДаДаДаБазовый outbound flow через единый API. Для VK outbound text идет через messages.send по peer_id из inbound webhook.
Photocontent.type = media, attachments[].type = photoДаДаДаДаPhoto outbound уже работает и для telegram_qr_bridge, и для telegram_bot: QR-режим шлет через user session, bot-режим через Bot API sendPhoto. Для VK фото идет через photos.getMessagesUploadServer + photos.saveMessagesPhoto и подтверждено на prod smoke.
Videocontent.type = media, attachments[].type = videoДаДаДаДаVideo outbound уже работает и для telegram_qr_bridge, и для telegram_bot: QR-режим использует Telegram client sendFile, bot-режим — Bot API sendVideo. MAX и WhatsApp используют свои upload adapters. В VK video сейчас идет как file-like вложение через doc upload flow и подтверждено на prod smoke.
Document / filecontent.type = media, attachments[].type = fileДаДаДаДаFile/document outbound уже поддержан и для telegram_qr_bridge, и для telegram_bot; в bot-режиме это Bot API sendDocument. В WhatsApp это document, в MAX file upload. В VK это doc upload flow через docs.getMessagesUploadServer + docs.save, подтвержденный на prod smoke.
AudioСпециальный v1 тип пока без отдельного contractЧастичноЧастичноНетДаДля Telegram audio outbound уже работает в обоих режимах: telegram_qr_bridge и telegram_bot. Общий TG-статус здесь partial только потому, что sticker по-прежнему не включен. Для MAX это пока скорее file-like upload без отдельной аудио-семантики. В VK обычный audio сейчас не поддержан: raw provider flow не дал стабильного upload/save surface.
VoiceСпециальный v1 тип пока без отдельного contractЧастичноЧастичноДаДаДля Telegram QR voice outbound работает через voiceNote. В telegram_bot Bridge вызывает Bot API sendVoice, но Telegram может отклонить доставку в конкретном чате с VOICE_MESSAGES_FORBIDDEN, поэтому TG-статус здесь остается partial. В MAX это пока общий file-like сценарий. В VK voice идет через docs.getMessagesUploadServer(type = audio_message) + docs.save и подтверждено на prod smoke.
Animation / GIFСпециальный v1 тип пока без отдельного contractЧастичноЧастичноЧастичноДаДля Telegram animation/GIF outbound уже работает в обоих режимах: QR через sendFile, bot через Bot API sendAnimation. Для MAX это пока file-like upload без отдельной GIF-семантики. В VK current implementation GIF/file-like вложения отправляются через doc upload flow.
StickerОтдельный outbound тип v1НетНетНетДаДля WhatsApp sticker уже выделен как отдельный outbound media type. Для Telegram и MAX отдельный sticker flow пока не поддержан.
Caption with mediacontent.text вместе с content.type = mediaЧастичноДаЧастичноДаCaption у первого вложения уже поддержан в обоих Telegram-режимах, а также в MAX и WhatsApp. Для VK caption/message уходит вместе с первым вложением.
Multiple attachments in one requestcontent.attachments[]ДаДаДаДаНесколько вложений уже отправляются последовательно в рамках одного API-запроса и для telegram_qr_bridge, и для telegram_bot. Для VK это тоже последовательная отправка нескольких provider messages и уже подтверждено на prod smoke.
Reply to existing messagereply_to.provider_message_idНетНетЧастичноДаВ WhatsApp Bridge уже отправляет quoted reply по reply_to.provider_message_id. Для VK Bridge уже прокидывает reply_to в messages.send; provider-side send подтвержден на prod smoke, но визуальная quoted-semantics в клиенте VK пока не выносится в yes. Для Telegram и MAX это пока не поддержано.
Forwarded sendОтдельный outbound тип v1НетНетНетНетПубличный API не поддерживает отправку пересланных сообщений как отдельный режим.
Reactions / likesОтдельный outbound тип v1НетНетНетНетUnified outbound reactions API пока не поддержан.
Read status updatesОтдельный outbound тип v1НетНетНетНетПубличный API не поддерживает отдельный mark as read / read receipt.

Authorization

API keys

Ключ выпускается в /my/api-keys внутри проекта. Во внешнем API передавайте его как bearer token: Authorization: Bearer brg_.... Значение показывается один раз, в базе хранится только hash.

messages:send

Обязателен для POST /v1/messages.

messages:read

Обязателен для GET /v1/messages/{id}.

channels:read

Зарезервирован для чтения каналов проекта.

channels:write

Зарезервирован для управления каналами проекта.

events:read

Зарезервирован для чтения inbound events и delivery logs.

webhooks:write

Зарезервирован для управления webhook endpoints.

Billing

Транзакционная модель

Одна транзакция = одно входящее событие message.inbound или одно успешно отправленное исходящее сообщение. Вложение внутри сообщения не считается отдельной транзакцией. Webhook retries, idempotency-повторы, duplicate inbound events и исходящие ошибки Bridge не увеличивают usage.

Оплата Premium и Pro идет через Ozon checkout в /my/billing, когда platform admin включил billing_payments_enabled и задал цены пакетов.

История платежей и ручная синхронизация pending-заказов доступны в /my/billing; platform admin видит все orders в /my/admin/billing.

Free: 500 сообщений в месяц.

Premium: 5000 сообщений в месяц.

Pro: 50000 сообщений в месяц и расширенные возможности.

Messages

Отправка и чтение сообщений

POST /v1/messages

Endpoint ставит outbound-сообщение в очередь. Новый запрос возвращает 202, повтор с тем же Idempotency-Key возвращает 200 и существующее сообщение.

curl -X POST https://bridge.murph.ru/v1/messages \
  -H "Authorization: Bearer brg_..." \
  -H "Idempotency-Key: 8b3f2d8c-7d8a-4e0c-9c8f-3b62d70f8b5a" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "channel-uuid",
    "to": { "external_id": "provider-chat-id" },
    "content": { "type": "text", "text": "Здравствуйте" }
  }'

GET /v1/messages/{id}

Endpoint возвращает outbound-сообщение в рамках проекта API key. Используйте его после 202, чтобы проверить status, providerMessageId, error, sentAt и исходный content.

curl https://bridge.murph.ru/v1/messages/message-uuid \
  -H "Authorization: Bearer brg_..."

Media outbound

content.type = media уже поддержан для telegram_qr_bridge, telegram_bot, MAX, WhatsApp и VK. Для Telegram сейчас доступны photo, video, audio, voice, animation, file; sticker для Telegram пока не включен, а voice в bot-mode может быть заблокирован самим Telegram. Для VK на prod уже подтверждены photo, video, document/file, voice и multiple attachments; audio и sticker сейчас явно не поддержаны. Размер ограничен platform setting media_max_attachment_bytes.

curl -X POST https://bridge.murph.ru/v1/messages \
  -H "Authorization: Bearer brg_..." \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "channel-uuid",
    "to": { "external_id": "provider-chat-id" },
    "content": {
      "type": "media",
      "text": "Подпись к файлу",
      "attachments": [
        {
          "type": "photo",
          "url": "https://example.com/image.jpg",
          "filename": "image.jpg"
        }
      ]
    }
  }'

Reply flow

Ответ с вложением на inbound webhook

Из webhook payload берите data.channel_id и data.chat.external_id. Именно data.chat.external_id является адресом диалога для ответа; data.from.external_id в группах может быть только участником, а не чатом.

В content.attachments[].url передавайте публичный http(s) URL файла, который Bridge сможет скачать с production-сервера. Можно использовать attachments[].url из inbound webhook только пока signed-ссылка не истекла; для отложенного ответа лучше скачать файл к себе и отдать свой URL.

Media outbound сейчас уже поддержан для telegram_qr_bridge, telegram_bot, max_qr_bridge, whatsapp_qr_bridge и vk_community. В Telegram partial остаются строки со sticker, reactions, read-contract и bot-voice, где итоговая доставка зависит от ограничений Telegram Bot API. В VK на текущем этапе уже подтверждены photo, video, document/file, voice и multiple attachments, а audio и sticker остаются no.

Если нужен Telegram через bot token, media outbound теперь тоже идет через тот же /v1/messages с content.type = media. Для Telegram sticker outbound пока остается вне публичной поддержки.

curl -X POST https://bridge.murph.ru/v1/messages \
  -H "Authorization: Bearer brg_..." \
  -H "Idempotency-Key: reply-<event_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "<webhook.data.channel_id>",
    "to": {
      "external_id": "<webhook.data.chat.external_id>"
    },
    "content": {
      "type": "media",
      "text": "Ответ с вложением",
      "attachments": [
        {
          "type": "photo",
          "url": "https://your-system.example.com/files/reply.jpg",
          "filename": "reply.jpg"
        }
      ]
    }
  }'

Webhooks

Webhook events

Webhook endpoints создаются в /my/webhooks. Bridge отправляет событие на активные endpoints проекта, подписанные на нужные event types: message.inbound, message.deleted, message.read, message.interaction и другие служебные события. Доставка идет через BullMQ: до 5 попыток с exponential backoff.

Повтор доставки доступен в кабинете: откройте /my/activity/events/{event_id} и используйте retry одной delivery или Повторить failed deliveries для endpoints, у которых latest delivery по событию сейчас failed.

Content-Type: application/json

User-Agent: Bridge-Webhook/0.1

X-Bridge-Event-Id: <event_id>

X-Bridge-Event-Type: <event_type>

X-Bridge-Timestamp: <unix_seconds>

X-Bridge-Signature: v1=<hmac_sha256_hex>

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

Подписывается raw body, а не распарсенный JSON. Timestamp нужно проверять на допустимое окно времени на стороне получателя.

signed_payload = X-Bridge-Timestamp + '.' + raw_request_body
expected = 'v1=' + hmac_sha256_hex(webhook_secret, signed_payload)
secure_compare(expected, X-Bridge-Signature)

message.inbound: reply / forwarded

В message.inbound поля message.reply_to и message.forwarded уже входят в стабильный v1 contract. Telegram и WhatsApp заполняют их best-effort, а MAX пока оставляет эти поля как reserved slots.

{
  "event_id": "event-uuid",
  "type": "message.inbound",
  "project_id": "project-uuid",
  "provider": "telegram",
  "occurred_at": "2026-04-24T10:00:00.000Z",
  "data": {
    "channel_id": "channel-uuid",
    "channel_type": "telegram_qr_bridge",
    "message": {
      "id": "123",
      "text": "Здравствуйте",
      "type": "text",
      "has_media": false,
      "date": "2026-04-24T10:00:00.000Z",
      "reply_to": null,
      "forwarded": null
    },
    "from": {
      "external_id": "telegram-user-id",
      "display_name": "Иван Петров"
    },
    "chat": {
      "external_id": "telegram-chat-id",
      "type": "private",
      "display_name": "Иван Петров"
    },
    "attachments": []
  }
}

message.interaction

message.interaction уже публикуется для WhatsApp reactions на сообщения, которые были отправлены через Bridge. Для Telegram и MAX этот event пока остается неподключенным.

{
  "event_id": "event-uuid",
  "type": "message.interaction",
  "project_id": "project-uuid",
  "provider": "whatsapp",
  "occurred_at": "2026-04-24T10:05:00.000Z",
  "data": {
    "channel_id": "channel-uuid",
    "channel_type": "whatsapp_qr_bridge",
    "message": {
      "id": "message-uuid",
      "provider_message_id": "wamid.HBgLNQ..."
    },
    "actor": {
      "external_id": "79001234567@s.whatsapp.net",
      "display_name": null
    },
    "interaction": {
      "type": "reaction",
      "action": "added",
      "key": "🔥"
    }
  }
}

message.read

message.read уже публикуется для WhatsApp read receipts и для Telegram read best-effort по raw updates. Сейчас event привязан к сообщениям, которые Bridge сам отправил во внешний канал.

{
  "event_id": "event-uuid",
  "type": "message.read",
  "project_id": "project-uuid",
  "provider": "whatsapp",
  "occurred_at": "2026-04-24T10:06:00.000Z",
  "data": {
    "channel_id": "channel-uuid",
    "channel_type": "whatsapp_qr_bridge",
    "message": {
      "id": "message-uuid",
      "provider_message_id": "wamid.HBgLNQ..."
    },
    "reader": {
      "external_id": "79001234567@s.whatsapp.net",
      "display_name": null
    },
    "read": {
      "read_at": "2026-04-24T10:06:00.000Z"
    }
  }
}

message.deleted

message.deleted уже публикуется для WhatsApp revoke/delete. Bridge best-effort находит исходное сообщение по provider_message_id, поэтому в data.message.id может прийти внутренний Bridge id удаленного сообщения.

{
  "event_id": "event-uuid",
  "type": "message.deleted",
  "project_id": "project-uuid",
  "provider": "whatsapp",
  "occurred_at": "2026-04-29T08:10:00.000Z",
  "data": {
    "channel_id": "channel-uuid",
    "channel_type": "whatsapp_qr_bridge",
    "message": {
      "id": "bridge-message-uuid",
      "provider_message_id": "AC481C91312A67417E4EC4733127442E"
    },
    "actor": {
      "external_id": null,
      "display_name": null
    },
    "deleted": {
      "deleted_at": "2026-04-29T08:10:00.000Z",
      "scope": "for_everyone",
      "direction": "outbound"
    }
  }
}

Attachments

Общий media contract

Входящие вложения Telegram, MAX, WhatsApp и current VK media layer сохраняются в Bridge storage. Во webhook приходит attachments[] со статусом, metadata и временной signed-ссылкой /v1/attachments/:id.

available - файл сохранен и доступен по attachments[].url.

rejected - файл отклонен политикой платформы, например по размеру.

failed - Bridge не смог скачать файл или сохранить его в storage.

Прямые временные URL мессенджеров наружу не отдаются.