Tài liệu / Webhook/ Nhận Webhook

Nhận Webhook

Webhook Cập nhật: 23/03/2026

Nhận Webhook

ThueAPI gửi HTTP POST request đến URL webhook của bạn khi có giao dịch mới.

Method & Endpoint

POST https://your-domain.com/your-webhook-path
Content-Type: application/json
X-Webhook-Signature: <hmac-sha256-signature>

Headers

HeaderMô tả
Content-Typeapplication/json
X-Webhook-SignatureHMAC-SHA256 chữ ký toàn bộ payload dùng Secret Key

Payload

ThueAPI gửi HTTP POST với Content-Type application/json:

{
  "transactions": [
    {
      "id": "3521",
      "gateway": "ACB",
      "transactionDate": "2025-10-14 10:59:30",
      "transactionNumber": "FT253146879VN",
      "accountNumber": "19354957",
      "content": "NAPTIEN MOMO 963869789",
      "transferType": "IN",
      "transferAmount": 200000,
      "checksum": "a1b2c3d4e5f6..."
    }
  ]
}

Mô tả các trường

TrườngKiểuMô tả
idstringID giao dịch trong hệ thống ThueAPI
gatewaystringMã ngân hàng: ACB, VCB, TCB, v.v.
transactionDatestringThời gian giao dịch (Y-m-d H:i:s)
transactionNumberstringMã giao dịch duy nhất từ ngân hàng
accountNumberstringSố tài khoản nhận/gửi
contentstringNội dung chuyển khoản
transferTypestringIN (tiền vào) hoặc OUT (tiền ra)
transferAmountintegerSố tiền (đơn vị: VND)
checksumstringMã duy nhất theo giao dịch — dùng để xác định giao dịch là duy nhất, tránh xử lý trùng lặp.

Headers

POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
X-Webhook-Signature: sha256=abc123def456...

Code mẫu xử lý Webhook

PHP (Laravel)

<?php
// routes/api.php
Route::post('/webhook/thueapi', [WebhookController::class, 'handle']);

// app/Http/Controllers/WebhookController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use App\Models\Transaction;

class WebhookController extends Controller
{
    public function handle(Request $request)
    {
        // Bước 1: Xác thực chữ ký HMAC
        $secret    = config('services.thueapi.webhook_secret');
        $payload   = $request->getContent();
        $signature = $request->header('X-Webhook-Signature');
        $expected  = hash_hmac('sha256', $payload, $secret);

        if (!hash_equals($expected, $signature)) {
            Log::warning('ThueAPI webhook: chữ ký không hợp lệ');
            return response()->json(['error' => 'Invalid signature'], 401);
        }

        // Bước 2: Verify checksum per-transaction (tuỳ chọn)
        // $txChecksum = $tx['checksum']; unset($tx['checksum']); ksort($tx);
        // $valid = hash_hmac('sha256', json_encode($tx), $secret) === $txChecksum;

        // Bước 3: Xử lý từng giao dịch
        $transactions = $request->input('transactions', []);
        foreach ($transactions as $tx) {
            // Kiểm tra trùng lặp theo transactionNumber
            $exists = Transaction::where('transaction_number', $tx['transactionNumber'])->exists();
            if ($exists) {
                Log::info('ThueAPI: bỏ qua giao dịch trùng #' . $tx['transactionNumber']);
                continue;
            }

            // Lưu vào database
            Transaction::create([
                'transaction_number' => $tx['transactionNumber'],
                'gateway'            => $tx['gateway'],
                'account_number'     => $tx['accountNumber'],
                'transfer_type'      => $tx['transferType'],
                'transfer_amount'    => $tx['transferAmount'],
                'content'            => $tx['content'],
                'transaction_date'   => $tx['transactionDate'],
            ]);
        }

        // Bước 3: Phản hồi thành công — BẮT BUỘC
        return response()->json(['success' => true]);
    }
}

PHP (Vanilla)

<?php
// webhook.php — Xử lý webhook từ ThueAPI.VN

$webhookSecret = 'YOUR_WEBHOOK_SECRET';
$payload       = file_get_contents('php://input');
$signature     = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';

// Xác thực chữ ký
$expected = hash_hmac('sha256', $payload, $webhookSecret);
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

$data = json_decode($payload, true);
if (!isset($data['transactions'])) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid payload']);
    exit;
}

// Kết nối database
$pdo = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass');
$stmt = $pdo->prepare('
    INSERT IGNORE INTO transactions
    (transaction_number, gateway, account_number, transfer_type, transfer_amount, content, transaction_date)
    VALUES (?, ?, ?, ?, ?, ?, ?)
');

foreach ($data['transactions'] as $tx) {
    $stmt->execute([
        $tx['transactionNumber'],
        $tx['gateway'],
        $tx['accountNumber'],
        $tx['transferType'],
        $tx['transferAmount'],
        $tx['content'],
        $tx['transactionDate'],
    ]);
}

http_response_code(200);
echo json_encode(['success' => true]);

Node.js (Express)

// webhook-handler.js — Express webhook receiver
const express = require('express');
const crypto  = require('crypto');

const app    = express();
const SECRET = 'YOUR_WEBHOOK_SECRET';

// Dùng raw body để tính HMAC chính xác
app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook/thueapi', (req, res) => {
    const payload   = req.body; // Buffer
    const signature = req.headers['x-webhook-signature'];

    // Xác thực chữ ký HMAC-SHA256
    const expected = crypto
        .createHmac('sha256', SECRET)
        .update(payload)
        .digest('hex');

    if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
        return res.status(401).json({ error: 'Invalid signature' });
    }

    const data = JSON.parse(payload.toString());
    const { transactions } = data;

    // Xử lý bất đồng bộ — không chặn response
    setImmediate(async () => {
        for (const tx of transactions) {
            console.log(`[${tx.transactionDate}] ${tx.transferType} ${tx.transferAmount} VND`);
            console.log(`Nội dung: ${tx.content}`);
            // TODO: Lưu database, gửi thông báo, cập nhật đơn hàng
        }
    });

    // Phản hồi ngay lập tức
    res.json({ success: true });
});

app.listen(3000, () => console.log('Webhook server đang chạy trên port 3000'));

Python (Flask)

# webhook_handler.py — Flask webhook receiver
import hmac
import hashlib
from flask import Flask, request, jsonify

app    = Flask(__name__)
SECRET = b'YOUR_WEBHOOK_SECRET'

@app.route('/webhook/thueapi', methods=['POST'])
def handle_webhook():
    # Lấy raw body để tính HMAC chính xác
    payload   = request.get_data()
    signature = request.headers.get('X-Webhook-Signature', '')

    # Xác thực chữ ký HMAC-SHA256
    expected = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, signature):
        return jsonify({'error': 'Invalid signature'}), 401

    data = request.get_json(force=True)
    for tx in data.get('transactions', []):
        print(f"[{tx['transactionDate']}] {tx['transferType']} "
              f"{tx['transferAmount']:,} VND - {tx['content']}")
        # TODO: Lưu database

    return jsonify({'success': True})

if __name__ == '__main__':
    app.run(port=5000)

Go (net/http)

// main.go — Go webhook receiver
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

const webhookSecret = "YOUR_WEBHOOK_SECRET"

type Transaction struct {
    ID                string `json:"id"`
    Gateway           string `json:"gateway"`
    TransactionDate   string `json:"transactionDate"`
    TransactionNumber string `json:"transactionNumber"`
    AccountNumber     string `json:"accountNumber"`
    Content           string `json:"content"`
    TransferType      string `json:"transferType"`
    TransferAmount    int64  `json:"transferAmount"`
}

type WebhookPayload struct {
    Transactions []Transaction `json:"transactions"`
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, `{"error":"cannot read body"}`, http.StatusBadRequest)
        return
    }

    // Xác thực HMAC-SHA256
    signature := r.Header.Get("X-Webhook-Signature")
    mac := hmac.New(sha256.New, []byte(webhookSecret))
    mac.Write(body)
    expected := hex.EncodeToString(mac.Sum(nil))

    if !hmac.Equal([]byte(expected), []byte(signature)) {
        http.Error(w, `{"error":"invalid signature"}`, http.StatusUnauthorized)
        return
    }

    var payload WebhookPayload
    if err := json.Unmarshal(body, &payload); err != nil {
        http.Error(w, `{"error":"invalid json"}`, http.StatusBadRequest)
        return
    }

    for _, tx := range payload.Transactions {
        fmt.Printf("[%s] %s %d VND - %s\n",
            tx.TransactionDate, tx.TransferType, tx.TransferAmount, tx.Content)
        // TODO: Lưu database
    }

    w.Header().Set("Content-Type", "application/json")
    w.Write([]byte(`{"success":true}`))
}

func main() {
    http.HandleFunc("/webhook/thueapi", webhookHandler)
    fmt.Println("Webhook server chạy trên :8080")
    http.ListenAndServe(":8080", nil)
}

C# (ASP.NET)

// WebhookController.cs — ASP.NET Core
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

[ApiController]
[Route("webhook")]
public class WebhookController : ControllerBase
{
    private const string WebhookSecret = "YOUR_WEBHOOK_SECRET";

    [HttpPost("thueapi")]
    public async Task<IActionResult> Handle()
    {
        // Đọc raw body
        using var reader = new StreamReader(Request.Body, Encoding.UTF8);
        var payload = await reader.ReadToEndAsync();
        var signature = Request.Headers["X-Webhook-Signature"].ToString();

        // Xác thực HMAC-SHA256
        var key = Encoding.UTF8.GetBytes(WebhookSecret);
        using var hmac = new HMACSHA256(key);
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
        var expected = Convert.ToHexString(hash).ToLower();

        if (!CryptographicOperations.FixedTimeEquals(
            Encoding.UTF8.GetBytes(expected),
            Encoding.UTF8.GetBytes(signature)))
        {
            return Unauthorized(new { error = "Invalid signature" });
        }

        var data = JsonSerializer.Deserialize<WebhookPayload>(payload);
        foreach (var tx in data?.Transactions ?? [])
        {
            Console.WriteLine($"[{tx.TransactionDate}] {tx.TransferType} {tx.TransferAmount} VND");
            // TODO: Lưu database
        }

        return Ok(new { success = true });
    }
}

public record WebhookPayload(List<Transaction> Transactions);
public record Transaction(
    string Id, string Gateway, string TransactionDate,
    string TransactionNumber, string AccountNumber,
    string Content, string TransferType, long TransferAmount);

Java (Spring Boot)

// WebhookController.java — Spring Boot
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/webhook")
public class WebhookController {

    private static final String WEBHOOK_SECRET = "YOUR_WEBHOOK_SECRET";

    @PostMapping("/thueapi")
    public ResponseEntity<Map<String, Boolean>> handle(
            @RequestBody String payload,
            @RequestHeader("X-Webhook-Signature") String signature) throws Exception {

        // Xác thực HMAC-SHA256
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(WEBHOOK_SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
        String expected = HexFormat.of().formatHex(hash);

        if (!expected.equals(signature)) {
            return ResponseEntity.status(401).body(Map.of("success", false));
        }

        // Parse JSON (dùng Jackson/Gson)
        // ObjectMapper mapper = new ObjectMapper();
        // WebhookPayload data = mapper.readValue(payload, WebhookPayload.class);
        System.out.println("Webhook hợp lệ từ ThueAPI: " + payload);

        return ResponseEntity.ok(Map.of("success", true));
    }
}
ThueAPI.VN
Đăng nhập với Google
hoặc đăng nhập bằng email
Quên mật khẩu?

Chưa có tài khoản?

Đăng ký với Google
hoặc đăng ký bằng email

Bằng cách đăng ký, bạn đồng ý với Điều khoản dịch vụChính sách bảo mật của chúng tôi.

Đã có tài khoản?