Robokassa ResultURL на localhost: подпись, OK и повтор уведомления
Проверяем ResultURL Robokassa локально: Flask, подпись MD5 с Password 2 и Shp_, сумма счёта, ответ OK и диагностика через HTTPS-туннель.

ResultURL — серверный адрес для уведомления Robokassa об успешной оплате. При локальной разработке откройте обработчик через HTTPS-туннель, проверьте подпись и верните ответ вида OK9001, где 9001 — номер счёта. Возвращение покупателя на SuccessURL само по себе не подтверждает оплату.
Ниже — тестовый Flask-обработчик. Он проверяет подпись и сумму одного демонстрационного счёта, но не выдаёт товары и не активирует подписки. Для рабочего магазина нужна транзакция в вашей базе данных.
1. Подготовьте обработчик ResultURL
Пример рассчитан на метод POST и алгоритм MD5 в технических настройках тестового магазина. Для другого алгоритма замените функцию хэширования. Используйте тестовый Password 2; пароль №1 сюда не подходит.
Создайте app.py в отдельной папке:
import hashlib
import hmac
import os
from decimal import Decimal, InvalidOperation
from flask import Flask, Response, request
app = Flask(__name__)
PASSWORD_2 = os.environ["ROBO_PASSWORD_2"]
INVOICE = os.environ.get("DEMO_INVOICE", "9001")
AMOUNT = Decimal(os.environ.get("DEMO_AMOUNT", "199.00"))
@app.post("/webhook/robokassa")
def result_url():
out_sum = request.form.get("OutSum", "")
inv_id = request.form.get("InvId", "")
received = request.form.get("SignatureValue", "")
if not out_sum or not inv_id or not received:
return "Missing parameters", 400
if any(len(request.form.getlist(key)) != 1 for key in request.form):
return "Duplicate parameters", 400
# Preserve OutSum exactly as received when computing the signature.
base = f"{out_sum}:{inv_id}:{PASSWORD_2}"
for key in sorted(key for key in request.form if key.startswith("Shp_")):
base += f":{key}={request.form[key]}"
expected = hashlib.md5(base.encode("utf-8")).hexdigest()
if not hmac.compare_digest(expected, received.lower()):
return "Invalid signature", 403
if inv_id != INVOICE:
return "Unknown invoice", 404
try:
amount = Decimal(out_sum)
if not amount.is_finite() or amount != AMOUNT:
return "Amount mismatch", 400
except InvalidOperation:
return "Invalid amount", 400
# Demo acknowledgement only. No product is issued by this example.
print(f"Verified test notification: invoice={inv_id}", flush=True)
return Response(f"OK{inv_id}", mimetype="text/plain")
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8000, debug=False)
В Bash/zsh создайте окружение и запустите приложение:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install Flask
export ROBO_PASSWORD_2='тестовый-пароль-2-из-настроек-магазина'
export DEMO_INVOICE='9001'
export DEMO_AMOUNT='199.00'
python app.py
На Windows используйте WSL либо активируйте .venv и задайте переменные средствами PowerShell. Пароли не сохраняйте в коде, Git или опубликованных логах.
2. Проверьте подпись локально без платежа
В другом терминале с тем же ROBO_PASSWORD_2 сформируйте подпись искусственного уведомления:
SIGNATURE=$(python3 -c 'import hashlib, os; print(hashlib.md5(("199.00:9001:" + os.environ["ROBO_PASSWORD_2"]).encode()).hexdigest())')
curl -i http://127.0.0.1:8000/webhook/robokassa --data-urlencode 'OutSum=199.00' --data-urlencode 'InvId=9001' --data-urlencode "SignatureValue=${SIGNATURE}"
Ожидаются статус 200 и тело OK9001. Повтор тоже возвращает OK9001. С произвольной подписью ожидается 403, без обязательного параметра — 400. Эта проверка подтверждает работу кода, но не доставку от Robokassa и не реальную оплату.
3. Откройте localhost через HTTPS
Для лимитов Free добавьте публичный SSH-ключ в кабинет, затем запустите:
ssh -p 2222 -o ExitOnForwardFailure=yes -R 80:127.0.0.1:8000 tunnelhunt.ru
Скопируйте HTTPS-адрес из терминала. Повторите POST из предыдущего шага, заменив локальный адрес на https://YOUR-SUBDOMAIN.tunnelhunt.ru/webhook/robokassa. Он должен вернуть то же OK9001. Браузер отправляет GET, поэтому открытие ссылки не проверяет этот POST-обработчик.
Если снаружи приходит 502, сначала проверьте Flask локально и порт в SSH. Полная диагностика — в инструкции доступа к localhost.
4. Настройте тестовое уведомление Robokassa
В технических настройках тестового магазина укажите полный ResultURL с путём /webhook/robokassa, метод POST, MD5 и соответствующий Password 2. Тестовые и рабочие пароли могут различаться. При создании тестового счёта используйте InvId=9001 и сумму 199.00 либо измените DEMO_INVOICE и DEMO_AMOUNT под ваш счёт и перезапустите Flask.
Пройдите тестовый платёж по инструкции Robokassa. Проверьте три результата: POST дошёл до локального обработчика, подпись и сумма приняты, сервер вернул OK с номером этого счёта. Формула подписи и ответ описаны в официальной документации ResultURL.
Почему ResultURL не подтверждается
| Симптом | Проверка |
|---|---|
405 Method Not Allowed |
В магазине выбран GET, а обработчик принимает POST |
403 Invalid signature |
Password 2, тестовый/рабочий режим, алгоритм хэша и полный набор Shp_ |
| Подпись не совпадает при одинаковой сумме | Для подписи используйте исходную строку OutSum, без округления и float |
400 Amount mismatch |
Подписанная сумма должна совпадать с ожидаемой суммой счёта |
404 Unknown invoice |
Проверьте InvId и счёт, который ожидает обработчик |
| После переподключения нет уведомлений | Изменился случайный домен; обновите ResultURL или закрепите адрес |
| Покупатель видит успех, но заказ не выдан | Проверьте серверный ResultURL и транзакцию выдачи |
Повтор уведомления и рабочий обработчик
Replay в кабинете позволяет повторить сохранённый тестовый запрос после изменения кода. В рабочей базе найдите счёт, сверьте сумму, заблокируйте запись, измените статус и выдайте продукт в одной транзакции. Для уже обработанного счёта верните подтверждение без повторной выдачи. Отвечайте OK{InvId} после успешного сохранения результата.
Flask-пример выше ничего не сохраняет: он подходит для проверки доставки и подписи, но не является системой учёта оплат.
Постоянный ResultURL для повторных тестов
На Pro за 199 ₽/месяц можно зарезервировать поддомен; квота — 50 ГБ в месяц. После активации в разделе «Туннели» выберите свободное имя, привяжите SSH-ключ и переподключитесь с ним. Проверьте адрес из терминала, затем сохраните его как ResultURL.
Это сохраняет адрес между сеансами разработки. Для доступности нужны Flask, SSH, включённый компьютер и сеть. Восстановление подключения — в инструкции autossh; другие провайдеры — в обзоре платёжных вебхуков.
Отладьте ResultURL на своём компьютере
Запустите тестовый обработчик и убедитесь, что он возвращает OK с номером счёта. Для повторных тестов закрепите поддомен, чтобы не менять ResultURL в настройках магазина.
Free: 1 ГБ трафика в месяц, 1 туннель, сессия до 4 часов. Pro: 199 ₽/месяц, 50 ГБ, до 5 туннелей и резервирование поддомена. Компьютер, приложение и SSH-соединение должны работать; постоянный адрес сохраняет имя, а не доступность выключенного сервера.
Готовы запустить свой первый туннель?
Начните использовать TunnelHunt бесплатно уже сегодня и откройте доступ к локальному серверу всего одной командой.
Зарегистрироваться бесплатно →По теме

Платёжные вебхуки на localhost: ЮKassa, Т-Банк, Robokassa и CloudPayments
7 сентября 2025 г.
Как проверить доставку платёжного вебхука на локальный сервер: HTTPS-туннель, выбор инструкции провайдера, подпись, повтор и диагностика HTTP-ответов.

Как повторить вебхук: отладка HTTP-запросов через Replay
20 июня 2026 г.
Получите тестовый вебхук, найдите ошибку и повторите запрос после исправления. Рабочий пример на Python, диагностика подписей и ограничения Replay.

Как открыть localhost в интернете: публичный HTTPS через SSH
11 сентября 2026 г.
Пошагово открываем локальный сайт из интернета без настройки роутера: SSH-туннель, проверка ссылки, Docker, Vite и диагностика ошибок.

autossh: постоянный SSH-туннель, переподключение и systemd
16 июня 2026 г.
Настройка autossh с ServerAliveInterval и systemd: восстановление SSH-туннеля, проверка подключения и диагностика Broken pipe без обещаний вечного uptime.