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

# Xoay Signing Secret

> Xoay signing secret webhook của bạn một cách an toàn với cửa sổ ân hạn ký kép 24 giờ

export const PlansAvailable = ({plans = [], maxPackRequired}) => {
  let displayPlans = plans;
  if (plans && plans.length === 1) {
    const lowerCasePlan = plans[0].toLowerCase();
    if (lowerCasePlan === "business") {
      displayPlans = ["Launch", "Business", "Enterprise"];
    } else if (lowerCasePlan === "premium") {
      displayPlans = ["Premium", "Launch", "Business", "Enterprise"];
    }
  }
  return <Note>
Available on {displayPlans.length === 1 ? "the " : ""}
{displayPlans.join(", ").replace(/\b\w/g, l => l.toUpperCase())}{" "}
{displayPlans.length > 1 ? "plans" : "plan"}.

{maxPackRequired && <span onClick={() => window.open('https://www.ayrshare.com/docs/additional/maxpack', '_self')} className="flex items-center mt-2 cursor-pointer">
 <span className="px-1.5 py-0.5 rounded text-sm" style={{
    backgroundColor: '#C264B6',
    color: 'white',
    fontSize: '12px'
  }}>
   Max Pack required
 </span>
</span>}
</Note>;
};

export const HeaderAPI = ({noProfileKey, profileKeyRequired}) => <>
    <ParamField header="Authorization" type="string" required>
      <a href="/docs/apis/overview#authorization">API Key</a> of the Primary Profile.
      <br />
      <br />
      Format: <code>Authorization: Bearer API_KEY</code>
    </ParamField>
    {!noProfileKey && (profileKeyRequired ? <ParamField header="Profile-Key" type="string" required>
          <a href="/docs/apis/overview#profile-key-format">Profile Key</a> of a User Profile.
          <br />
          <br />
          Format: <code>Profile-Key: PROFILE_KEY</code>
        </ParamField> : <ParamField header="Profile-Key" type="string">
          <a href="/docs/apis/overview#profile-key-format">Profile Key</a> of a User Profile.
          <br />
          <br />
          Format: <code>Profile-Key: PROFILE_KEY</code>
        </ParamField>)}
  </>;

<PlansAvailable plans={["premium"]} maxPackRequired={false} />

## Tổng quan

Ayrshare ký mọi lần gửi webhook bằng [HMAC-SHA256](https://en.wikipedia.org/wiki/HMAC) của payload, sử dụng **signing secret** của bạn làm khóa, để bên nhận của bạn có thể xác nhận rằng lần gửi thực sự đến từ Ayrshare. Xem [Bảo mật Webhook](/docs/apis/webhooks/overview#webhook-security) để biết cách xác thực hoạt động.

Việc xoay signing secret cho phép bạn thay thế nó theo lịch trình định kỳ, hoặc ngay lập tức nếu bạn nghi ngờ nó đã bị lộ. Để việc xoay được an toàn, Ayrshare mở một **cửa sổ ân hạn 24 giờ** sau mỗi lần xoay, trong đó các lần gửi được ký bằng **cả** secret trước đây và secret mới của bạn. Điều này cho phép bạn cập nhật bên nhận theo lịch trình của riêng mình mà không bị rớt hay từ chối bất kỳ lần gửi nào — cùng một mẫu hình được sử dụng bởi Stripe và GitHub.

<Note>
  Signing secret là **phạm vi profile**: có một secret duy nhất cho mỗi User Profile (UID),
  và nó ký **mọi** webhook action mà profile đó đã đăng ký. Không có
  signing secret riêng cho từng action — việc đặt hoặc xoay secret sẽ thay đổi nó cho tất cả
  các action trên profile đó cùng một lúc.
</Note>

## Xoay từ Dashboard

Bạn có thể đặt hoặc xoay signing secret của mình từ [trang Webhooks](https://app.ayrshare.com/webhooks) trong Developer Dashboard. Bảng **Signing Secret** xuất hiện phía trên danh sách webhook của bạn khi profile đã có ít nhất một webhook đăng ký.

<Steps>
  <Step title="Mở bảng Signing Secret">
    Đi đến [trang Webhooks](https://app.ayrshare.com/webhooks). Nếu chưa có secret nào được cấu hình, bảng sẽ hiển thị **No signing secret configured** với nút **Set Signing Secret**. Nếu đã có một secret được cấu hình, bảng sẽ hiển thị **Signing secret configured** với nút **Rotate**.
  </Step>

  <Step title="Đặt hoặc Xoay">
    Nhấp **Set Signing Secret** (lần đầu) hoặc **Rotate** (secret hiện có). Một modal sẽ mở ra với một secret ngẫu nhiên mạnh được tạo sẵn và hiển thị. Bạn có thể **Copy** nó, **Regenerate** một cái mới, hoặc bật **paste my own** để cung cấp giá trị của riêng bạn.
  </Step>

  <Step title="Xác nhận">
    Sao chép secret vào một nơi an toàn — nó chỉ được hiển thị một lần và không bao giờ có thể truy xuất từ UI nữa — sau đó xác nhận để gửi. Một toast thành công sẽ xuất hiện và bảng sẽ cập nhật.
  </Step>

  <Step title="Cập nhật bên nhận của bạn">
    Khi xoay (không phải đặt lần đầu), bảng sẽ hiển thị chỉ báo cửa sổ ân hạn đang hoạt động và nút **Rotate** bị vô hiệu hóa cho đến khi cửa sổ đóng lại. Bạn có 24 giờ để triển khai secret mới đến bên nhận của mình.
  </Step>
</Steps>

## Xoay qua API

Xoay (hoặc đặt) signing secret bằng một lệnh gọi duy nhất. Lệnh này tạo một secret mới, trỏ tham chiếu secret của profile đến nó, và — khi đã có một secret hiện có — ghi lại secret bị thay thế làm secret trước đây với thời hạn hết 24 giờ.

### Tham số Header

<HeaderAPI />

### Tham số Body

<ParamField body="secret" type="string" required>
  Giá trị signing secret mới. Bất kỳ chuỗi không rỗng nào cũng được chấp nhận. Chúng tôi khuyến nghị một giá trị ngẫu nhiên dài, có entropy cao (ví dụ: 32 byte ngẫu nhiên mã hóa dưới dạng base64url).
</ParamField>

<RequestExample>
  ```bash cURL theme={"system"}
  curl --request POST \
    --url https://api.ayrshare.com/api/hook/webhook/secret \
    --header 'Authorization: Bearer YOUR_API_KEY' \
    --header 'Content-Type: application/json' \
    --header 'Profile-Key: YOUR_PROFILE_KEY' \
    --data '{
      "secret": "your-new-signing-secret"
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200: Response theme={"system"}
  {
    "status": "success",
    "action": "webhook",
    "refId": "3dc079614bdc3f281d9" // User Profile Ref Id
  }
  ```
</ResponseExample>

`secret` dạng plaintext **không bao giờ** được trả về trong phản hồi và không bao giờ được ghi log. Phản hồi mang `refId` hướng đến client (một hash của UID), không bao giờ là chính UID. Header `Profile-Key` là tùy chọn và giới hạn phạm vi xoay cho một User Profile duy nhất cho các tài khoản đa profile.

Một `secret` thiếu hoặc rỗng sẽ trả về lỗi được ánh xạ (`code: 101`, "Missing/incorrect parameter") với trạng thái HTTP `400`, và không thay đổi nào được thực hiện đối với secret hiện tại của bạn. Việc đặt lần đầu qua API (không có secret hiện có) tạo secret mà không có secret trước đây nào được ghi lại và không có cửa sổ ân hạn.

## Quy trình Xoay An toàn

Nhờ cửa sổ ân hạn 24 giờ, không có thứ tự thao tác bắt buộc nào — bên nhận của bạn vẫn tiếp tục hoạt động trong suốt quá trình. Chuỗi thao tác được khuyến nghị là:

<Steps>
  <Step title="Xoay secret">
    Xoay từ dashboard hoặc qua API. Ayrshare ngay lập tức bắt đầu ký các lần gửi bằng cả secret trước đây và secret mới của bạn.
  </Step>

  <Step title="Cập nhật bên nhận của bạn">
    Trong vòng 24 giờ, triển khai secret mới đến bên nhận webhook của bạn để nó xác thực dựa trên giá trị mới.
  </Step>

  <Step title="Để cửa sổ đóng lại">
    Sau 24 giờ, Ayrshare tự động xóa secret trước đây và chỉ ký bằng secret mới. Không cần thao tác thêm nào từ phía bạn.
  </Step>
</Steps>

<Tip>
  Nếu bạn xoay lại trong khi cửa sổ ân hạn vẫn đang mở, secret vừa bị thay thế
  trở thành secret trước đây mới và một cửa sổ 24 giờ mới bắt đầu. Chỉ
  một secret trước đây được giữ tại một thời điểm.
</Tip>

## Xác thực Chữ ký Trong Cửa sổ Ân hạn

Bên ngoài cửa sổ ân hạn, các lần gửi đã ký mang các header tiêu chuẩn (xem [Bảo mật Webhook](/docs/apis/webhooks/overview#webhook-security)):

```bash theme={"system"}
X-Authorization-Timestamp : <Unix Timestamp In Seconds>
X-Authorization-Content-SHA256 : <current-sig>
X-Authorization-Content-SHA256-V2 : v1=<current-sig>
```

Trong cửa sổ 24 giờ sau khi xoay, header `X-Authorization-Content-SHA256-V2` mới liệt kê **cả hai** chữ ký, hiện tại trước, phân tách bằng dấu phẩy:

```bash theme={"system"}
X-Authorization-Timestamp : <Unix Timestamp In Seconds>
X-Authorization-Content-SHA256 : <current-sig>
X-Authorization-Content-SHA256-V2 : v1=<current-sig>,v1=<previous-sig>
```

<Note>
  `X-Authorization-Content-SHA256` không thay đổi: nó luôn mang HMAC của
  secret hiện tại duy nhất, để tương thích ngược. Chữ ký kép chỉ xuất hiện trong
  header `X-Authorization-Content-SHA256-V2` mới.
</Note>

Mỗi giá trị trong `X-Authorization-Content-SHA256-V2` được tiền tố bằng một scheme tag. `v1=` biểu thị chữ ký HMAC-SHA256, được tính chính xác giống như `X-Authorization-Content-SHA256`. Header `-V2` luôn hiện diện mỗi khi một lần gửi được ký — nó mang ít nhất `v1=<current-sig>` — vì vậy bạn có thể dựa vào nó như một hợp đồng bên nhận ổn định.

Để xác thực một lần gửi trong (hoặc ngoài) một lần xoay:

<Steps>
  <Step title="Tính HMAC">
    Tính HMAC-SHA256 của **body request thô** bằng signing secret được cấu hình cục bộ của bạn.
  </Step>

  <Step title="So sánh với mọi chữ ký được liệt kê">
    Đọc `X-Authorization-Content-SHA256-V2`, tách nó theo dấu phẩy, loại bỏ tiền tố `v1=` khỏi mỗi giá trị, và chấp nhận lần gửi là hợp lệ nếu HMAC bạn tính khớp với **bất kỳ** chữ ký `v1=` nào được liệt kê.
  </Step>
</Steps>

Việc chấp nhận nếu **bất kỳ** chữ ký nào được liệt kê khớp chính là điều làm cho việc xoay không có downtime: một bên nhận vẫn được cấu hình với secret cũ sẽ khớp `v1=<previous-sig>`, trong khi một bên nhận đã cập nhật lên secret mới sẽ khớp `v1=<current-sig>` — cả hai đều thành công trong suốt cửa sổ.

### Ví dụ Xác thực Bên nhận

```javascript Node.js theme={"system"}
import crypto from "crypto";

// secret is the signing secret currently configured on your receiver.
function isAuthenticWebhook(rawBody, headers, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody) // the raw, unparsed request body
    .digest("hex");

  const headerValue = headers["x-authorization-content-sha256-v2"] || "";

  // Accept if ANY v1= signature in the header matches our computed HMAC.
  return headerValue
    .split(",")
    .map((part) => part.trim())
    .filter((part) => part.startsWith("v1="))
    .map((part) => part.slice("v1=".length))
    .some((sig) => {
      const sigBuf = Buffer.from(sig);
      const expectedBuf = Buffer.from(expected);
      // timingSafeEqual throws on length mismatch — treat as not authentic.
      return (
        sigBuf.length === expectedBuf.length &&
        crypto.timingSafeEqual(sigBuf, expectedBuf)
      );
    });
}
```

<Warning>
  Luôn tính HMAC trên các byte body request **thô**, chính xác như
  đã nhận — không phải trên một đối tượng JSON được tuần tự hóa lại. Việc tuần tự hóa lại có thể thay đổi
  khoảng trắng hoặc thứ tự khóa và làm hỏng việc xác thực. Sử dụng so sánh thời gian không đổi
  (chẳng hạn như `crypto.timingSafeEqual`) để tránh các cuộc tấn công timing.
</Warning>

Nếu bản ghi secret hiện tại của một lần gửi bị thiếu, lần gửi vẫn tiếp tục **không được ký** (không có header chữ ký) thay vì thất bại. Nếu chỉ bản ghi secret trước đây bị mất, chữ ký trước đây sẽ bị bỏ qua và chữ ký hiện tại vẫn được phát ra trong cả hai header.
