Скип на основной контент

Уэбхуки

Веб-хуки позволяют 4pay.online Чтобы продвигать обновления статуса транзакций в режиме реального времени на ваш сервер — опрос не требуется.


Как это работает​

  1. Оператор платформы назначает уведомление URL В вашем терминале
  2. Когда статус транзакции изменяется, платформа отправляет подписанное сообщение. POST к этому URL
  3. Ваш сервер отвечает на любые 2xx для подтверждения получения
  4. Все остальное — 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_idstringУникальный для каждого события. Используйте его в качестве ключа дедупликации
timestampintegerUnix секунды, когда была сформирована доставка
typestringtransaction.<status>
dataobjectОперационная полезная нагрузка

data поля​

Полное полеТипОписание
typestringТип сделки: payment, payout
txidstringВаш первоначальный заказ ID
idstringПлатформенная транзакция UUID
amountstringОригинальная сумма в наименьшей валютной единице
amount_deststringОплата за проезд (после оплаты)
amount_with_feestringСумма, включая плату за платформу
statusstringНовый статус сделки
error_descriptionstringОписание ошибки (пустота на успех)

Сумма равна десятичным струныНо не цифры — а 18-десятичный актив не подходит a JSON Безопасный номер.

Значения статуса в webhooks​

статусФиналЗначение
creatednoТранзакция, созданная в ожидании обработки
chargingnoОбработка в процессе
action_requirednoТребуемое действие плательщика
chargedда.Успешно установлен
rejectedда.Снижение лимитов, борьба с мошенничеством или поставщик
failedда.Ошибка обработки, включая отмену до урегулирования
reversedда.Урегулированная сделка была возвращена
refundednoВозврат средств завершен — окончательность еще находится на рассмотрении

Перечень выше не закрыт: рельсы несут свои промежуточные состояния (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

Формальный контракт​

Эта страница - практическое руководство. Нормативное определение конверта, подпись, расписание повторных попыток и словарь статуса - это Универсальный протокол транзакций.