PayFS Documentation
Phát triển

Webhook

Cách nhận thông báo giao dịch từ PayFS qua webhook.

Khi tài khoản ngân hàng có biến động, PayFS gửi một HTTP POST tới endpoint của bạn.

Chưa dựng endpoint bao giờ, đọc Bắt đầu nhanh trước.

Payload

{
  "account_id": "1418079746853494784",
  "amount": 14000,
  "bank": "MB",
  "bank_account_number": "0933723830",
  "content": "NGUYEN VAN A chuyen tien  Ma giao dich  Trace773231",
  "transaction_date": "2025-09-15T15:02:00.000Z",
  "transaction_id": "1418108930751619072",
  "transfer_type": "credit"
}
TrườngKiểuMô tả
transaction_idstringID giao dịch, duy nhất toàn hệ thống. Dùng làm khóa chống trùng.
account_idstringID tài khoản ngân hàng trong PayFS
amountnumberSố tiền, đơn vị VND, luôn là số nguyên dương
transfer_typestringcredit là tiền vào, debit là tiền ra
contentstringNội dung chuyển khoản do ngân hàng trả về
transaction_datestringThời điểm giao dịch, ISO 8601 theo giờ UTC
bank_account_numberstringSố tài khoản nhận hoặc gửi
bankstringMã ngân hàng: MB, ACB, OCB

amount luôn dương. Dùng transfer_type để biết tiền vào hay ra, đừng dựa vào dấu.

Sự kiện

Khi tạo webhook, bạn chọn sự kiện muốn nhận.

Sự kiệnKhi nàoPayload
transaction.creditTiền vào tài khoảnNhư trên
transaction.debitTiền ra khỏi tài khoảnNhư trên
order.successĐơn hàng thanh toán thành côngXem dưới
order.failedĐơn hàng thanh toán thất bạiXem dưới

Hai sự kiện order.* dùng cấu trúc khác:

{
  "order_id": "1418108930751619072",
  "amount": 250000,
  "status": "success",
  "customer_email": "khachhang@example.com",
  "created_at": "2025-09-15T15:02:00.000Z"
}

Chỉ order_id, amount và status là bắt buộc. Ba trường còn lại có thể vắng.

order.success và order.failed là sự kiện webhook của Workspace. Chúng không đổi trong bản này. Callback của một Liên kết thanh toán là HTTPS POST riêng tới Callback URL của liên kết đó, không phải hai sự kiện này. Xem Liên kết thanh toán.

PayFS gửi kèm 5 header sau mỗi lần gọi.

HeaderVí dụDùng để
X-Client-API-Keypk_1234...Xác thực nguồn gọi. Luôn kiểm tra.
X-PayFS-Signature86f02cef...Chữ ký HMAC-SHA256, hex thô 64 ký tự
X-PayFS-Timestamp1758173916UNIX timestamp, dùng chung với chữ ký
X-PayFS-Webhook-ID1418108...ID của webhook endpoint
X-PayFS-Attempt1Lần gửi thứ mấy

Kiểm tra API key là bắt buộc. Kiểm tra chữ ký là tùy chọn nhưng nên làm, xem Chữ ký webhook.

Xác thực bằng API key

app.post('/payfs-webhook', (req, res) => {
  const apiKey = req.headers['x-client-api-key'];

  if (!apiKey || apiKey !== process.env.PAYFS_WEBHOOK_API_KEY) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  handleTransaction(req.body);
  res.status(200).json({ status: 'ok' });
});
<?php
$apiKey = $_SERVER['HTTP_X_CLIENT_API_KEY'] ?? null;

if (!$apiKey || !hash_equals($_ENV['PAYFS_WEBHOOK_API_KEY'], $apiKey)) {
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}

$transaction = json_decode(file_get_contents('php://input'), true);
handleTransaction($transaction);

http_response_code(200);
echo json_encode(['status' => 'ok']);

Chống xử lý trùng

PayFS gửi lại khi thất bại, nên cùng một giao dịch có thể tới nhiều lần. Dùng transaction_id làm khóa chống trùng.

if (await daXuLy(tx.transaction_id)) {
  return res.status(200).json({ status: 'already_processed' });
}
await danhDauDaXuLy(tx.transaction_id);

Lưu vào cơ sở dữ liệu hoặc Redis, đừng lưu trong bộ nhớ tiến trình.

Gửi lại khi thất bại

PayFS coi là thành công khi endpoint trả mã 2xx.

Thông sốMặc định
Thời gian chờ mỗi lần gửi30 giây
Số lần thử tối đa10
Khoảng chờ lần đầu10 giây
Hệ số tăng khoảng chờ2

Endpoint phải trả lời trong 30 giây. Xử lý lâu thì trả 200 trước rồi đẩy việc sang hàng đợi nền. Trả lời chậm bị tính là thất bại và PayFS sẽ gửi lại.

Mã trạng thái nên trả về

Tình huốngMã
Xử lý xong200
Thiếu hoặc sai API key401
Payload không hợp lệ400
Lỗi tạm thời phía bạn500, PayFS sẽ gửi lại

Xử lý sự cố

Thiếu header X-Client-API-Key Đa số framework chuẩn hóa tên header về chữ thường. Đọc x-client-api-key.

Sai API key Đối chiếu lại với giá trị trong Client Portal, đồng thời kiểm tra chuỗi không bị cắt khi truyền.

Webhook không được gọi Kiểm tra URL truy cập được từ Internet, đã chọn đúng sự kiện, và tường lửa không chặn. Lịch sử gửi trong Client Portal cho biết PayFS đã thử gửi chưa và nhận được mã phản hồi nào.

On this page