Webhook thông báo kết quả thanh toán
Khi có giao dịch thanh toán thành công qua QR Code tĩnh hoặc QR Code động, hệ thống TINGEE sẽ tự động gửi một thông báo Webhook tới địa chỉ URL mà bạn đã đăng ký trong cấu hình.
Thông báo đẩy dữ liệu giao dịch từ Tingee về hệ thống của đối tác ngay khi tiền vào tài khoản.
Header gửi kèm
Các header này giúp bạn định danh và xác thực tính toàn vẹn của dữ liệu nhận được.
| Header | Mô tả |
|---|---|
Content-Type | Luôn là application/json. |
x-request-id | Mã định danh duy nhất cho mỗi yêu cầu webhook (UUID). |
x-request-timestamp | Thời gian gửi thông báo (format: yyyyMMddHHmmssSSS, múi giờ UTC+7). |
x-signature | Chữ ký HMAC SHA512 dùng để kiểm tra tính hợp lệ của dữ liệu. |
Body Parameter
Dữ liệu chi tiết về giao dịch được gửi trong phần body.
| Trường | Kiểu | Mô tả |
|---|---|---|
clientId | string | Client ID của đối tác. |
transactionCode | string | Mã giao dịch hệ thống. |
amount | number | Số tiền thanh toán thực tế. |
content | string | Nội dung chuyển khoản hoặc ghi chú. |
bank | string | Tên ngân hàng nhận. |
accountNumber | string | Số tài khoản ngân hàng nhận. |
vaAccountNumber | string | Số tài khoản ảo (nếu có). |
transactionDate | string | Thời gian giao dịch (format: yyyyMMddHHmmss). |
additionalData | array | Chứa thông tin bổ sung (ví dụ: billId cho QR động). |
Xác thực Webhook
Bạn cần xác thực chữ ký x-signature để đảm bảo webhook này thực sự đến từ Tingee.
Chữ ký được tạo bằng: HMAC_SHA512(timestamp + ":" + json_body, secretKey).
Ví dụ mã nguồn xác thực
- NestJS
- C#
- Java
- PHP
import { createHmac } from 'crypto';
function verifyWebhook(headers: any, body: any, secretKey: string): boolean {
const signature = headers['x-signature'];
const timestamp = headers['x-request-timestamp'];
const message = `${timestamp}:${JSON.stringify(body)}`;
const computedSig = createHmac('sha512', secretKey).update(message).digest('hex');
return computedSig === signature;
}
using System;
using System.Security.Cryptography;
using System.Text;
public class WebhookVerifier
{
public static bool VerifyWebhook(string body, string timestamp, string signature, string secretKey)
{
var message = $"{timestamp}:{body}";
var keyBytes = Encoding.UTF8.GetBytes(secretKey);
var messageBytes = Encoding.UTF8.GetBytes(message);
using (var hmac = new HMACSHA512(keyBytes))
{
var hash = hmac.ComputeHash(messageBytes);
var computedSig = BitConverter.ToString(hash).Replace("-", "").ToLower();
return computedSig == signature;
}
}
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class WebhookVerifier {
public static boolean verifySignature(String body, String timestamp, String signature, String secretKey) throws Exception {
String message = timestamp + ":" + body;
Mac hmac = Mac.getInstance("HmacSHA512");
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA512");
hmac.init(keySpec);
byte[] hash = hmac.doFinal(message.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : hash) {
sb.append(String.format("%02x", b));
}
return sb.toString().equals(signature);
}
}
function verifyWebhook($headers, $body, $secretKey) {
$timestamp = $headers['x-request-timestamp'] ?? '';
$signature = $headers['x-signature'] ?? '';
$message = $timestamp . ':' . $body;
$computedSig = hash_hmac('sha512', $message, $secretKey);
return hash_equals($computedSig, $signature);
}
Phản hồi yêu cầu
Sau khi xử lý thành công, máy chủ của bạn cần phản hồi với mã HTTP 200 OK và nội dung JSON như sau:
{
"code": "00",
"message": "Success"
}
Cơ chế Retry & Logging
Hệ thống Tingee luôn nỗ lực đảm bảo bạn nhận được thông báo:
- Retry: Nếu máy chủ của bạn phản hồi lỗi hoặc không phản hồi (timeout), Tingee sẽ tự động gửi lại webhook tối đa 5 lần, mỗi lần cách nhau 1 phút.
- Idempotency: Bạn nên sử dụng
transactionCodeđể kiểm tra xem giao dịch đã được xử lý trước đó chưa, tránh xử lý trùng lặp trong trường hợp nhận được webhook retry. - Audit Log: Chúng tôi khuyến cáo bạn ghi lại toàn bộ Payload và Header của webhook để dễ dàng tra soát khi có vấn đề phát sinh.
⚠️ Lưu ý — Giao dịch QR động
Đối với các giao dịch thanh toán qua QR động, người thực hiện giao dịch có thể tự ý thay đổi số tiền tại thời điểm quét mã, khiến số tiền thực nhận khác với số tiền được nhúng trong mã QR.
📌 Hiện tại hầu hết các ngân hàng đã không cho phép sửa số tiền khi thanh toán qua QR động. Tuy nhiên, vẫn còn một số ngân hàng chưa áp dụng hạn chế này nên rủi ro vẫn có thể xảy ra.
Trong trường hợp này, TINGEE vẫn ghi nhận giao dịch và gửi webhook về phía đối tác như bình thường.
Đối tác cần chủ động đối chiếu và xác minh giao dịch thông qua các nguồn bổ sung như:
- Ảnh chụp màn hình giao dịch của khách hàng
- Sao kê tài khoản ngân hàng thực nhận
Chỉ xác nhận giao dịch hoàn tất sau khi đã kiểm tra khớp số tiền thực tế với giá trị hóa đơn.
Tuyệt đối không để lộ secretKey trong mã nguồn và chỉ xử lý giao dịch khi chữ ký x-signature hoàn toàn hợp lệ.