Формат уведомлений
Когда платёж оплачен или отменён, мы отправляем POST-запрос на адрес уведомлений вашего магазина. Адрес задаётся при подключении магазина.
Когда отправляются
| payment_status | Событие |
|---|---|
success | Платёж оплачен. Можно выдавать заказ |
cancel | Платёж отменён: истёк срок оплаты или банк покупателя отклонил перевод |
В редких случаях банк покупателя подтверждает перевод уже после истечения срока оплаты. Тогда после
cancel придёт success по тому же платежу — деньги получены, заказ нужно выдать.Запрос
POST на адрес уведомлений магазина, Content-Type: application/json.
Тело
{
"id": 1482093517,
"amount": 1500.0,
"currency": "RUB",
"uuid": "order-1024",
"payment_status": "success"
}
| Поле | Тип | Описание |
|---|---|---|
id | integer | ID платежа |
amount | number | Сумма платежа в рублях |
currency | string | Валюта: RUB |
uuid | string | Номер заказа в вашей системе |
payment_status | string | success или cancel |
Заголовки
| Поле | Тип | Описание |
|---|---|---|
X-Zefir-Signature | string | Подпись уведомления: sha256=<hex> |
X-Zefir-Timestamp | string | Время отправки, Unix-время в секундах. Участвует в подписи |
X-Zefir-Delivery | string | Идентификатор уведомления (UUID). При повторной отправке не меняется |
Проверка подписи
Подпись — HMAC-SHA256 от строки <X-Zefir-Timestamp>.<тело запроса> с секретным ключом магазина в качестве ключа. Тело берите в исходном виде, до разбора JSON.
- Посчитайте ожидаемую подпись и сравните её с заголовком
X-Zefir-Signatureфункцией сравнения за постоянное время. - Отклоняйте уведомления, у которых
X-Zefir-Timestampотличается от текущего времени больше чем на 5 минут — это защищает от повторной отправки перехваченного запроса.
$secretKey = getenv('ZEFIRPAY_SECRET_KEY');
$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_ZEFIR_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_ZEFIR_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secretKey);
if (!hash_equals($expected, $signature) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
if ($event['payment_status'] === 'success') {
// Найдите заказ по $event['uuid'], сверьте сумму и выдайте заказ один раз.
}
http_response_code(200);
import hashlib
import hmac
import time
def verify(body: bytes, timestamp: str, signature: str, secret_key: str) -> bool:
expected = "sha256=" + hmac.new(
secret_key.encode(), f"{timestamp}.".encode() + body, hashlib.sha256
).hexdigest()
fresh = abs(time.time() - int(timestamp or 0)) <= 300
return fresh and hmac.compare_digest(expected, signature)
const crypto = require("crypto");
const express = require("express");
const app = express();
app.post("/zefirpay/webhook", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.get("X-Zefir-Timestamp") || "";
const signature = req.get("X-Zefir-Signature") || "";
const expected =
"sha256=" +
crypto
.createHmac("sha256", process.env.ZEFIRPAY_SECRET_KEY)
.update(`${timestamp}.`)
.update(req.body)
.digest("hex");
const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) &&
Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
if (!valid) return res.sendStatus(401);
const event = JSON.parse(req.body);
// Найдите заказ по event.uuid, сверьте сумму и выдайте заказ один раз.
res.sendStatus(200);
});
Ответ вашего сервера
Ответьте кодом 2xx в течение 10 секунд. Тело ответа не важно.
Если ваш сервер не ответил или ответил другим кодом, мы повторим отправку через 1 минуту, 5 минут, 15 минут, 1 час, 3 часа, 6 часов, 12 часов и 24 часа. Уведомление можно запросить заново вручную — см. Повторная отправка.
Рекомендации
- Одно и то же уведомление может прийти несколько раз. Обрабатывайте его так, чтобы повтор не выдал заказ второй раз: например, отмечайте обработанные
idплатежей. - Сверяйте
uuidиamountс заказом в своей системе. - Если уведомление вызывает сомнения или не пришло, запросите данные платежа: статус в ответе API — окончательный.
- Страница, на которую покупатель возвращается после оплаты, не подтверждает оплату: покупатель может закрыть её или открыть раньше времени.