Уэбхуки
Веб-хуки позволяют 4pay.online Чтобы продвигать обновления статуса транзакций в режиме реального времени на ваш сервер — опрос не требуется.
Как это работает
- Оператор платформы назначает уведомление URL В вашем терминале
- Когда статус транзакции изменяется, платформа отправляет подписанное сообщение.
POSTк этому URL - Ваш сервер отвечает на любые
2xxдля подтверждения получения - Все остальное — non-2xx, тайм-аут, ошибка соединения — запускает повторную попытку.
Настройка конечной точки webhook
Уведомление URL is a настройка терминала, настроенный один раз в админ
Консоль (notify_url), и это относится ко всем транзакциям на этом терминале.
Поле уведомления по запросу отсутствует: a notifyUrl В творящем теле есть
Если вам нужны разные направления для разного трафика, используйте
Различные терминалы.
Рядом с ним URLУстановить web-cook секретный на терминале. Без него Платформа не отправляет подписи, и у вас нет способа сказать правду. Уведомление от подделанного.
Секрет также может быть установлен на партнерской учетной записи, а не на терминале. Спроси своего оператора, какой из них подписывает ваши поставки - проверка Против неправильного секрета проваливается именно как подделка.
Полезная нагрузка Webcook
Данные о транзакциях поступают в конверте. Прочитайте ваши поля
dataНе с самого верхнего уровня.
{
"event_id": "4d9d0e6c-1d0a-4a2e-9a2f-0c9a1b0e8f21",
"timestamp": 1772668800,
"type": "transaction.charged",
"data": {
"type": "payment",
"txid": "order-12345",
"id": "550e8400-e29b-41d4-a716-446655440000",
"amount": "1000",
"amount_dest": "950",
"amount_with_fee": "1050",
"status": "charged",
"error_description": ""
}
}
конверт
| Полное поле | Тип | Описание |
|---|---|---|
event_id | string | Уникальный для каждого события. Используйте его в качестве ключа дедупликации |
timestamp | integer | Unix секунды, когда была сформирована доставка |
type | string | transaction.<status> |
data | object | Операционная полезная нагрузка |
data поля
| Полное поле | Тип | Описание |
|---|---|---|
type | string | Тип сделки: payment, payout |
txid | string | Ваш первоначальный заказ ID |
id | string | Платформенная транзакция UUID |
amount | string | Оригинальная сумма в наименьшей валютной единице |
amount_dest | string | Оплата за проезд (после оплаты) |
amount_with_fee | string | Сумма, включая плату за платформу |
status | string | Новый статус сделки |
error_description | string | Описание ошибки (пустота на успех) |
Сумма равна десятичным струныНо не цифры — а 18-десятичный актив не подходит a JSON Безопасный номер.
Значения статуса в webhooks
| статус | Финал | Значение |
|---|---|---|
created | no | Транзакция, созданная в ожидании обработки |
charging | no | Обработка в процессе |
action_required | no | Требуемое действие плательщика |
charged | да. | Успешно установлен |
rejected | да. | Снижение лимитов, борьба с мошенничеством или поставщик |
failed | да. | Ошибка обработки, включая отмену до урегулирования |
reversed | да. | Урегулированная сделка была возвращена |
refunded | no | Возврат средств завершен — окончательность еще находится на рассмотрении |
Перечень выше не закрыт: рельсы несут свои промежуточные состояния
(cancelled, payment_suspended, pre_authorized_3ds и другие, и один может
Относитесь к любому статусу, который вы не признаете. неокончательный и держать
Ждать — строгий энум потерпит неудачу на первом необычном рельсе.
Выполняйте свою бизнес-логику только в окончательном статусе. Три конечных результата отличаются. Что случилось с деньгами:
charged— расчеты состоялись, средства ваши;rejectedиfailed— никаких расчетов, средства плательщика были освобождены;reversed- поселение произошло и было возвращено.chargedИтак, ваша бизнес-логика уже работает:reversedВам нужно отменять (напомните о доставке, отключите услугу).
Отмена до прибытия урегулирования как failedНикогда, как reversed.
Проверка подписи
Каждая подписанная доставка имеет три заголовка:
x-webhook-event-id: 4d9d0e6c-1d0a-4a2e-9a2f-0c9a1b0e8f21
x-webhook-timestamp: 1772668800
x-webhook-signature: 6b1f… HMAC-SHA256, hex
Подпись является HMAC-SHA256 над струной <timestamp>.<raw body>Ключевой
Две детали решают, проверить ли ваш чек
работает:
- подписывать необработанный орган запроса точно так же, как полученный - перепрофилирование JSON изменяет байты и ломает сравнение;
- подписывать Весь конвертНет.
data.
Отказать в доставке, которая timestamp находится более чем в пяти минутах от вашего
Сравнивайте свои часы и подписи в постоянном времени.
Ответить на webhook
Ответить на любой 2xxТело игнорируется — пустое 200 является действительным
Признание.
Все остальное - статус non-2xx, тайм-аут, отказ в соединении - считается как Неудачи и запланируйте повторную попытку.
Обработайте веб-хук асинхронно. Немедленно признайте квитанцию и обработайте бизнес-логику в фоновой работе. Долгосрочные обработчики могут отсрочить и вызвать дублирование поставок.
Политика повторного использования
Неудачные поставки восстанавливаются после 30 секунды, 2 минуты, 10 минуты, 1 час и 6 часыПосле шестой попытки доставка припаркована для ручной работы. Проверка и больше не повторяется автоматически.
Сделайте своего куратора идемпотентным: одно и то же событие может наступить более одного раза.
Дублировать на **event_id**Не в отношении сделки UUID Одна транзакция
Он производит несколько событий в течение своей жизни, и они совершенно разные.
Если заказ имеет значение для вас, прочитайте транзакцию
сзади сзади GET /api/v1/transactions/:id Вместо того, чтобы довериться приказу о прибытии.
Тестирование webhooks локально
Используйте инструмент туннеля (ngrok, localtunnel, Cloudflare Tunnel), чтобы открыть локальный сервер в Интернете во время разработки:
ngrok http 3000
# → Forwarding: https://abc123.ngrok.io → localhost:3000
Попросите вашего оператора установить https://abc123.ngrok.io/webhooks/payment как
notify_url на испытательном терминале.
Видишь? Песочница и тестирование Инструкции по тестированию транзакций.
Пример обработчика (Node.js)
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post(
'/webhooks/payment',
express.raw({ type: 'application/json' }), // raw body — needed for the signature
(req, res) => {
const timestamp = req.get('x-webhook-timestamp');
const signature = req.get('x-webhook-signature') ?? '';
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(`${timestamp}.${req.body}`)
.digest('hex');
// Compare lengths BEFORE timingSafeEqual: it throws a RangeError on buffers
// of different sizes, so a forged header would produce a 500 with a stack
// trace instead of a 401.
const ok =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) &&
Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
if (!ok) return res.sendStatus(401);
const { event_id, data } = JSON.parse(req.body);
// Deduplicate on event_id, then hand off to a background job
// processPaymentEvent(event_id, data);
console.log(`Transaction ${data.txid} (${data.id}): ${data.status}`);
res.sendStatus(200);
}
);
app.listen(3000);
Пример обработчика (Python)
import hashlib
import hmac
import json
import os
import time
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhooks/payment', methods=['POST'])
def webhook():
raw = request.get_data() # raw body — needed for the signature
timestamp = request.headers.get('x-webhook-timestamp', '')
signature = request.headers.get('x-webhook-signature', '')
expected = hmac.new(
os.environ['WEBHOOK_SECRET'].encode(),
f"{timestamp}.{raw.decode()}".encode(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(signature, expected):
return '', 401
if abs(time.time() - int(timestamp or 0)) > 300:
return '', 401
envelope = json.loads(raw)
data = envelope['data']
# Deduplicate on envelope['event_id'], then hand off to a background job
print(f"Transaction {data['txid']} ({data['id']}): {data['status']}")
return '', 200
Формальный контракт
Эта страница - практическое руководство. Нормативное определение конверта, подпись, расписание повторных попыток и словарь статуса - это Универсальный протокол транзакций.