> ## Documentation Index
> Fetch the complete documentation index at: https://docs-agents.fpt.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Xác thực chữ ký

> Ký và xác thực mọi gói tin của kênh API bằng HMAC-SHA256

Mọi gói tin ở **cả hai chiều** đều được ký bằng HMAC-SHA256 với khóa ký của kênh. Chữ ký đi trong header `X-Hub-Signature-256` với tiền tố `sha256=`, kèm header `X-Hub-Timestamp` mang thời điểm gửi.

```http theme={null}
X-Hub-Timestamp: 1755763200
X-Hub-Signature-256: sha256=<HMAC-SHA256 dạng hex chữ thường>
```

## Cách tạo chữ ký

Chuỗi được ký là **timestamp + dấu chấm + phần thân nguyên bản**:

```text theme={null}
mac = HMAC-SHA256(secret, timestamp + "." + rawRequestBody)
X-Hub-Signature-256 = "sha256=" + hex_chữ_thường(mac)
```

* **Ký trên đúng chuỗi byte đã gửi đi.** Chuyển JSON thành đối tượng rồi tuần tự hóa lại, kể cả khi chỉ đổi thứ tự khóa, sẽ cho ra chữ ký khác.
* **Timestamp nằm trong chữ ký**, nên kẻ bắt được gói tin không thể sửa nó. Gửi thời gian Unix hiện tại tính bằng **giây**.
* Timestamp lệch quá **5 phút** so với giờ máy chủ, **theo cả hai hướng**, đều bị từ chối. Hãy đồng bộ đồng hồ máy chủ bằng NTP.
* **Cơ chế không có ngoại lệ.** Không có chế độ bỏ qua chữ ký, và không có miễn trừ cho kết nối chưa tạo khóa.

### Ví dụ tạo chữ ký

<CodeGroup>
  ```go Golang theme={null}
  package main

  import (
  	"crypto/hmac"
  	"crypto/sha256"
  	"encoding/hex"
  	"fmt"
  	"strconv"
  	"time"
  )

  func sign(secret string, body []byte, timestamp string) string {
  	h := hmac.New(sha256.New, []byte(secret))
  	h.Write([]byte(timestamp))
  	h.Write([]byte("."))
  	h.Write(body)
  	return "sha256=" + hex.EncodeToString(h.Sum(nil))
  }

  func main() {
  	// Phần thân nguyên bản: chính chuỗi byte sẽ được gửi đi.
  	body := []byte(`{"integrationKey":"api_7Kd2xQ9mPz4vR8nLcJt3Aw",` +
  		`"messageId":"msg-1001","user":{"id":"cust-42"},` +
  		`"text":"Xin chao"}`)
  	secretKey := "my_secret_key"
  	timestamp := strconv.FormatInt(time.Now().Unix(), 10)

  	fmt.Println("X-Hub-Timestamp:", timestamp)
  	fmt.Println("X-Hub-Signature-256:", sign(secretKey, body, timestamp))
  }
  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function sign(secret, body, timestamp) {
    return 'sha256=' + crypto
      .createHmac('sha256', secret)
      .update(timestamp + '.' + body)
      .digest('hex');
  }

  // Phần thân nguyên bản: chính chuỗi sẽ được gửi đi.
  const body = JSON.stringify({
    integrationKey: 'api_7Kd2xQ9mPz4vR8nLcJt3Aw',
    messageId: 'msg-1001',
    user: { id: 'cust-42' },
    text: 'Xin chao'
  });
  const secretKey = 'my_secret_key';
  const timestamp = Math.floor(Date.now() / 1000).toString();

  console.log('X-Hub-Timestamp:', timestamp);
  console.log('X-Hub-Signature-256:', sign(secretKey, body, timestamp));
  ```
</CodeGroup>

## Xác thực chữ ký chúng tôi gửi tới

Dùng lại đúng hàm `sign` ở trên, rồi so sánh bằng hàm so sánh **thời gian hằng số** để không rò rỉ thông tin qua thời gian xử lý:

```javascript Node.js (Express) theme={null}
const express = require('express');
const crypto = require('crypto');
const app = express();

// Bắt buộc: giữ lại phần thân NGUYÊN BẢN để tính chữ ký.
app.use(express.raw({ type: 'application/json' }));

app.post('/agent-replies', (req, res) => {
  const ts = req.get('X-Hub-Timestamp') || '';
  const got = req.get('X-Hub-Signature-256') || '';
  const raw = req.body; // Buffer, chưa qua JSON.parse

  // 1. Kiểm tra độ lệch thời gian trước (tối đa 5 phút).
  const skew = Math.abs(Math.floor(Date.now() / 1000) - Number(ts));
  if (!Number.isFinite(skew) || skew > 300) return res.sendStatus(401);

  // 2. Đối chiếu chữ ký.
  const want = sign(process.env.CHANNEL_SECRET, raw, ts);
  const a = Buffer.from(got), b = Buffer.from(want);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(raw.toString('utf8'));

  // 3. Trả 200 NGAY, rồi xử lý bất đồng bộ.
  res.sendStatus(200);
  handleAsync(event);
});
```

<Warning>
  Hầu hết lỗi chữ ký đến từ việc framework web đã tự chuyển phần thân thành đối tượng trước khi bạn kịp đọc. Hãy cấu hình để **giữ lại chuỗi byte nguyên bản** (`express.raw`, `bodyParser.raw`, hoặc đọc trực tiếp từ luồng đầu vào) rồi mới phân tích JSON sau khi đã xác thực xong.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.