Appearance
🪝 Tạo Hook
🔐 Token là gì?
Token được tạo một lần duy nhất trong quá trình khai báo Hook trên hệ thống Pay2S.vn. Token giúp xác minh rằng dữ liệu giao dịch được gửi từ Pay2S là chính xác và hợp lệ.
Định dạng Request Header
Content-Type: application/json
Authorization: Bearer <SecretKey>⚠️ Lưu ý: Token này chỉ được hiển thị duy nhất một lần khi bạn tạo Hook. Lưu giữ an toàn!
🛠️ Cách tạo Webhook
1️⃣ Truy cập Webhooks
- Vào khu vực
Webhooks→Thêm webhook - Bạn sẽ thấy giao diện như sau:

2️⃣ Nhập các thông tin cần thiết
| Trường | Bắt buộc | Mô tả |
|---|---|---|
| Tài khoản | ✓ | Tài khoản liên kết sẽ gửi dữ liệu |
| Sự kiện | ✓ | Loại giao dịch (tất cả, nhận, chuyển) |
| Endpoint | ✓ | URL nhận dữ liệu (http/https) |
3️⃣ Mô tả chi tiết
Chọn tài khoản
- Chọn tài khoản ngân hàng đã liên kết
- Lưu ý: Bạn cần liên kết tài khoản trước khi tạo Hook
Chọn sự kiện
- Tất cả: Gửi cả giao dịch nhận và chuyển
- Nhận tiền: Chỉ gửi khi có tiền vào
- Chuyển tiền: Chỉ gửi khi có tiền ra
Endpoint
- URL địa chỉ nhận dữ liệu từ Pay2S
- Dùng
https://nếu website có SSL - Dùng
http://nếu không có SSL - Phải trả về HTTP 200 để xác nhận
🔁 Chu kỳ gửi dữ liệu
⏱️ Cơ chế Retry
- Gửi ngay: Ngay lập tức khi có giao dịch
- Retry 1: Sau 60 giây nếu chưa nhận được 200
- Retry 2-5: Tiếp tục retry 5 lần nếu lỗi
- Dừng: Khi nhận được HTTP 200 hoặc sau lần gửi thứ 6
✅ Điều kiện dừng
Pay2S sẽ ngừng gửi nếu:
- Nhận được HTTP 200 hoặc
- Response body chứa:
json
{
"success": true
}💻 Ví dụ xử lý Webhook
php
<?php
// Lấy header Authorization
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';
// Kiểm tra token
$token = 'YOUR_SECRET_KEY_HERE';
if ($authHeader !== 'Bearer ' . $token) {
http_response_code(401);
echo json_encode(['success' => false]);
exit;
}
// Lấy dữ liệu JSON từ body
$data = json_decode(file_get_contents('php://input'), true);
// Xử lý dữ liệu giao dịch
$id = $data['id'];
$transactionNumber = $data['transactionNumber'];
$amount = $data['transferAmount'];
$content = $data['content'];
// Lưu vào database
// INSERT INTO transactions (id, transaction_number, amount, content) VALUES ($id, $transactionNumber, $amount, $content);
// Trả về response
http_response_code(200);
echo json_encode(['success' => true]);
?>javascript
const express = require('express');
const app = express();
app.use(express.json());
const SECRET_KEY = 'YOUR_SECRET_KEY_HERE';
app.post('/webhook', (req, res) => {
// Kiểm tra header Authorization
const authHeader = req.headers['authorization'] || '';
if (authHeader !== `Bearer ${SECRET_KEY}`) {
return res.status(401).json({ success: false });
}
// Lấy dữ liệu từ body
const { id, transactionNumber, transferAmount, content } = req.body;
// Xử lý dữ liệu giao dịch
console.log('Transaction received:', {
id,
transactionNumber,
transferAmount,
content
});
// Lưu vào database
// await Transaction.create({ id, transactionNumber, transferAmount, content });
// Trả về response
return res.status(200).json({ success: true });
});
app.listen(3000, () => console.log('Webhook server running on port 3000'));python
from flask import Flask, request, jsonify
import os
app = Flask(__name__)
SECRET_KEY = 'YOUR_SECRET_KEY_HERE'
@app.route('/webhook', methods=['POST'])
def webhook():
# Kiểm tra header Authorization
auth_header = request.headers.get('Authorization', '')
if auth_header != f'Bearer {SECRET_KEY}':
return jsonify({'success': False}), 401
# Lấy dữ liệu JSON
data = request.get_json()
transaction_id = data.get('id')
transaction_number = data.get('transactionNumber')
amount = data.get('transferAmount')
content = data.get('content')
# Xử lý dữ liệu giao dịch
print(f'Transaction: {transaction_id}, Amount: {amount}')
# Lưu vào database
# db.transaction.insert_one({ 'id': transaction_id, 'transaction_number': transaction_number, 'amount': amount, 'content': content })
return jsonify({'success': True}), 200
if __name__ == '__main__':
app.run(port=5000, debug=False)java
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import com.google.gson.Gson;
import java.util.Map;
@RestController
@RequestMapping("/webhook")
public class WebhookController {
private static final String SECRET_KEY = "YOUR_SECRET_KEY_HERE";
@PostMapping
public ResponseEntity<?> handleWebhook(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@RequestBody Map<String, Object> data) {
// Kiểm tra header Authorization
if (authHeader == null || !authHeader.equals("Bearer " + SECRET_KEY)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(Map.of("success", false));
}
// Lấy dữ liệu giao dịch
String id = (String) data.get("id");
String transactionNumber = (String) data.get("transactionNumber");
Long amount = ((Number) data.get("transferAmount")).longValue();
String content = (String) data.get("content");
// Xử lý dữ liệu
System.out.println("Transaction: " + id + ", Amount: " + amount);
// Lưu vào database
// transactionService.save(new Transaction(id, transactionNumber, amount, content));
return ResponseEntity.ok(Map.of("success", true));
}
}go
package main
import (
"encoding/json"
"log"
"net/http"
"strings"
)
const SECRET_KEY = "YOUR_SECRET_KEY_HERE"
type WebhookData struct {
ID string `json:"id"`
TransactionNumber string `json:"transactionNumber"`
TransferAmount int64 `json:"transferAmount"`
Content string `json:"content"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
// Kiểm tra Authorization header
authHeader := r.Header.Get("Authorization")
if authHeader != "Bearer "+SECRET_KEY {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]bool{"success": false})
return
}
// Đọc và parse JSON body
var data WebhookData
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]bool{"success": false})
return
}
// Xử lý dữ liệu giao dịch
log.Printf("Transaction received: %s, Amount: %d", data.ID, data.TransferAmount)
// Trả về response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func main() {
http.HandleFunc("/webhook", webhookHandler)
log.Println("Webhook server running on :8080")
http.ListenAndServe(":8080", nil)
}ruby
require 'sinatra'
require 'json'
SECRET_KEY = 'YOUR_SECRET_KEY_HERE'
post '/webhook' do
# Kiểm tra Authorization header
auth_header = request.headers['Authorization'] || ''
if auth_header != "Bearer #{SECRET_KEY}"
status 401
return { success: false }.to_json
end
# Lấy dữ liệu JSON
data = JSON.parse(request.body.read)
id = data['id']
transaction_number = data['transactionNumber']
amount = data['transferAmount']
content = data['content']
# Xử lý dữ liệu giao dịch
puts "Transaction received: #{id}, Amount: #{amount}"
# Lưu vào database
# Transaction.create(id: id, transaction_number: transaction_number, amount: amount, content: content)
status 200
{ success: true }.to_json
end📌 Test Webhook
Cách test
- Tạo giao dịch test trên hệ thống Pay2S
- Kiểm tra logs endpoint của bạn để xem dữ liệu nhận được
- Xem lịch sử tại mục Lịch sử giao dịch trên dashboard Pay2S
Công cụ test
Dùng Postman hoặc curl để test:
bash
curl -X POST https://your-endpoint.com/webhook \
-H "Authorization: Bearer YOUR_SECRET_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"id": "test123",
"gateway": "momo",
"transactionDate": "2024-01-25",
"transactionNumber": "TXN001",
"accountNumber": "0123456789",
"content": "Test payment",
"transferType": "IN",
"transferAmount": 100000,
"checksum": "abc123"
}'⚡ Best Practices
- Bảo mật: Lưu token ở environment variables, không hardcode
- Validation: Luôn kiểm tra Authorization header trước khi xử lý
- Idempotency: Xử lý được webhook gửi nhiều lần (lưu transaction ID)
- Response nhanh: Trả về 200 trong 30 giây, offload processing vào queue
- Logging: Log tất cả webhook để debug và audit
- HTTPS: Luôn dùng HTTPS cho endpoint
- Error handling: Đừng trả về 200 nếu có lỗi database
- Monitoring: Alert khi webhook failed hoặc timeout
- Database transaction: Dùng transaction để tránh race condition
- Rate limiting: Đừng bị dồn request từ Pay2S (nếu endpoint lỗi)
