> ## 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.

# 署名シークレットのローテーション

> 24 時間のデュアル署名グレースウィンドウで Webhook 署名シークレットを安全にローテーションします

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} />

## 概要

Ayrshare はすべての Webhook 配信について、**署名シークレット** をキーとしてペイロードの [HMAC-SHA256](https://en.wikipedia.org/wiki/HMAC) で署名します。これにより、受信側は配信が本当に Ayrshare からのものであることを確認できます。検証の仕組みについては [Webhook セキュリティ](/docs/apis/webhooks/overview#webhook-security) を参照してください。

署名シークレットをローテーションすると、定期的なスケジュールで、あるいは漏洩が疑われる場合には即座に置き換えることができます。ローテーションを安全に行うため、Ayrshare はローテーション後に **24 時間のグレースウィンドウ** を開き、その間の配信は **以前のシークレットと新しいシークレットの両方** で署名されます。これにより、1 件も配信を落としたり拒否したりすることなく、独自のスケジュールで受信側を更新できます — Stripe や GitHub と同じパターンです。

<Note>
  署名シークレットは **プロファイル全体で共通** です。1 つの User Profile (UID) につき
  1 つのシークレットがあり、そのプロファイルが登録している **すべて** の Webhook アクションに
  署名します。アクションごとの署名シークレットは存在しません — シークレットを設定または
  ローテーションすると、そのプロファイルのすべてのアクションで一度に変更されます。
</Note>

## ダッシュボードからローテーションする

Developer Dashboard の [Webhooks ページ](https://app.ayrshare.com/webhooks) から署名シークレットを設定またはローテーションできます。プロファイルに少なくとも 1 つの Webhook が登録されると、Webhook リストの上に **Signing Secret** パネルが表示されます。

<Steps>
  <Step title="Signing Secret パネルを開く">
    [Webhooks ページ](https://app.ayrshare.com/webhooks) に移動します。シークレットがまだ設定されていない場合、パネルには **No signing secret configured** と **Set Signing Secret** ボタンが表示されます。すでに設定されている場合は、**Signing secret configured** と **Rotate** ボタンが表示されます。
  </Step>

  <Step title="設定またはローテーション">
    **Set Signing Secret**(初回)または **Rotate**(既存のシークレット)をクリックします。強力でランダムに生成されたシークレットがあらかじめ入力され、表示された状態でモーダルが開きます。**Copy** でコピー、**Regenerate** で新しいものを生成、または **paste my own** を切り替えて独自の値を指定できます。
  </Step>

  <Step title="確認">
    シークレットを安全な場所にコピーしてください — 一度しか表示されず、UI から再取得することはできません — その後、確認して送信します。成功トーストが表示され、パネルが更新されます。
  </Step>

  <Step title="受信側を更新">
    ローテーション時(初回設定ではない場合)、パネルにはアクティブなグレースウィンドウのインジケーターが表示され、ウィンドウが閉じるまで **Rotate** ボタンは無効になります。新しいシークレットを受信側にデプロイするための時間は 24 時間です。
  </Step>
</Steps>

## API 経由でローテーションする

1 回の呼び出しで署名シークレットをローテーション(または設定)します。これにより、新しいシークレットが作成され、プロファイルのシークレット参照がそれに向け直され、既存のシークレットが存在した場合には、置き換えられたシークレットが 24 時間後に期限切れとなる「以前のシークレット」として記録されます。

### ヘッダーパラメータ

<HeaderAPI />

### ボディパラメータ

<ParamField body="secret" type="string" required>
  新しい署名シークレットの値です。空でない任意の文字列を受け付けます。長く、高エントロピーなランダム値(たとえば 32 バイトのランダムを 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` はレスポンスで **決して** 返されず、ログにも残りません。レスポンスには、クライアント向けの `refId`(UID のハッシュ)が含まれ、UID そのものは決して含まれません。`Profile-Key` ヘッダーは任意で、マルチプロファイルアカウントにおいて、ローテーションを単一の User Profile にスコープします。

`secret` が欠落または空の場合は、マップされたエラー(`code: 101`、"Missing/incorrect parameter")が HTTP `400` ステータスとともに返され、現在のシークレットには変更が加えられません。API 経由の初回設定(既存のシークレットなし)では、以前のシークレットは記録されず、グレースウィンドウなしでシークレットが作成されます。

## 安全なローテーション手順

24 時間のグレースウィンドウがあるため、操作の順序に厳密な決まりはありません — 受信側は全期間を通じて動作し続けます。推奨される手順は次のとおりです:

<Steps>
  <Step title="シークレットをローテーション">
    ダッシュボードまたは API 経由でローテーションします。Ayrshare は直ちに、以前のシークレットと新しいシークレットの両方で配信に署名し始めます。
  </Step>

  <Step title="受信側を更新">
    24 時間以内に、新しいシークレットで検証を行うよう Webhook 受信側にデプロイします。
  </Step>

  <Step title="ウィンドウを閉じる">
    24 時間後、Ayrshare は以前のシークレットを自動的にクリアし、新しいシークレットのみで署名します。お客様側での追加作業は不要です。
  </Step>
</Steps>

<Tip>
  グレースウィンドウがまだ開いている間に再度ローテーションすると、直前に置き換えられた
  シークレットが新しい「以前のシークレット」となり、新たな 24 時間のウィンドウが始まります。
  一度に保持される「以前のシークレット」は 1 つだけです。
</Tip>

## グレースウィンドウ中の署名検証

グレースウィンドウ外では、署名された配信は標準のヘッダーを持ちます([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>
```

ローテーション後の 24 時間ウィンドウ中、新しい `X-Authorization-Content-SHA256-V2` ヘッダーには **両方** の署名が、現在の署名を先頭にカンマ区切りで列挙されます:

```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` は変更されません: 後方互換性のため、常に
  現在のシークレットによる単一の HMAC を保持します。デュアル署名は新しい
  `X-Authorization-Content-SHA256-V2` ヘッダーにのみ表示されます。
</Note>

`X-Authorization-Content-SHA256-V2` の各値には、スキームタグが接頭辞として付きます。`v1=` は、`X-Authorization-Content-SHA256` と全く同じように計算される HMAC-SHA256 署名を示します。`-V2` ヘッダーは配信が署名されるときには常に存在し、少なくとも `v1=<current-sig>` を保持するため、安定した受信側のコントラクトとして依拠できます。

ローテーション中(またはローテーション外で)配信を検証するには:

<Steps>
  <Step title="HMAC を計算">
    ローカルに設定された署名シークレットを使用して、**生のリクエストボディ** の HMAC-SHA256 を計算します。
  </Step>

  <Step title="列挙されたすべての署名と比較">
    `X-Authorization-Content-SHA256-V2` を読み取り、カンマで分割し、各値から `v1=` の接頭辞を取り除いて、計算した HMAC が列挙された `v1=` 署名の **いずれか** と一致すれば、その配信を本物として受け入れます。
  </Step>
</Steps>

**いずれか** の列挙された署名と一致すれば受け入れる、というのがローテーションをゼロダウンタイムにする仕組みです: 古いシークレットが設定されたままの受信側は `v1=<previous-sig>` に一致し、新しいシークレットに更新された受信側は `v1=<current-sig>` に一致します — ウィンドウ中はどちらも成功します。

### 受信側の検証例

```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>
  HMAC は必ず、受信した **生の** リクエストボディのバイトそのものに対して計算してください
  — 再シリアライズした JSON オブジェクトに対してではありません。再シリアライズは
  空白やキー順を変えて検証を壊す可能性があります。タイミング攻撃を避けるため、
  (`crypto.timingSafeEqual` のような)定時間比較を使用してください。
</Warning>

配信の現在のシークレットレコードが見つからない場合、その配信は失敗ではなく **未署名**(署名ヘッダーなし)として進行します。以前のシークレットレコードのみが失われている場合、以前の署名はスキップされ、現在の署名は引き続き両方のヘッダーに出力されます。
