# General Translation Platform: Webhooks URL: https://generaltranslation.com/ja/docs/platform/dashboard/reference/webhooks.mdx --- title: Webhooks description: Webhook エンドポイントを設定し、署名を検証して、General Translation からの翻訳イベントを処理します。 --- Webhooks は、署名付きの HTTP POST リクエストとして翻訳イベントをバックエンドに送信します。ファイルやジョブが変更されたときに、独自のワークフローを実行するために使用できます。 ## Webhook を作成する [#create-webhook] 1. **Organization Settings > Webhooks** に移動します。 2. **Create Webhook** をクリックします。 3. イベントを受信するエンドポイント URL を入力します。URL には HTTPS を使用する必要があります。 4. 購読するイベントタイプを選択します。 5. **Create** をクリックします。 エンドポイントを作成したら、Webhook 詳細ページを開いて signing secret をコピーします。このシークレットを使用して、受信したリクエストが General Translation から送信されたものであることを検証します。 Organization ごとに作成できる **Webhook エンドポイント** は最大 **5 件** です。 ## イベントタイプ [#event-types] 各エンドポイントは、1つ以上のイベントタイプを購読できます。購読設定は webhook 詳細ページから更新できます。 * `translated_file.completed` は、翻訳済みファイルの準備が完了し、ダウンロード可能になったときに発火します。 * `translated_file.edited` は、翻訳済みファイルが翻訳エディターでユーザーによって手動で編集されたときに発火します。 * `translation_job.completed` は、翻訳ジョブが完了したときに発火します。 ## ペイロード形式 [#payload-format] 各 webhook 配信は、JSON ボディを含む HTTP POST リクエストです。 ```json { "id": "evt_xxx", "type": "translated_file.completed", "created_at": "2026-04-30T12:00:00.000Z", "api_version": "2026-03-06.v1", "data": { "object": { "id": "file_xxx", "org_id": "org_xxx", "project_id": "project_xxx", "branch_id": "branch_xxx", "source_file_id": "src_xxx", "file_id": "file_xxx", "version_id": "ver_xxx", "locale": "fr", "file_format": "json", "data_format": null, "completed_at": "2026-04-30T12:00:00.000Z" } } } ``` 最上位の `id` は、配信の重複を排除するために使える、安定したイベント識別子です。 ## 署名を検証する [#verify-signatures] すべての webhook リクエストには、3 つのヘッダーが含まれます。 * `webhook-id` は、`evt_xxx` のようなイベント ID です。 * `webhook-timestamp` は、リクエストが送信された時刻を示す、秒単位の Unix タイムスタンプです。 * `webhook-signature` は、`v1,` 形式の署名です。 この署名は、[Standard Webhooks](https://www.standardwebhooks.com/) の規約に従っています。 リクエストを検証するには: 1. `{webhook-id}.{webhook-timestamp}.{raw request body}` を連結します。 2. `whsec_` プレフィックスを取り除いてから secret を Base64 デコードし、署名キーを取得します。 3. デコードした署名キーを使って、連結した文字列の HMAC-SHA256 を計算します。 4. 結果を Base64 エンコードします。 5. `v1,` プレフィックスの後ろにある署名値と比較します。 6. リプレイ攻撃を防ぐため、タイムスタンプが現在時刻から 5 分以内であることを確認します。 ### 例: Node.js ```js import crypto from "crypto"; function verifyWebhook(payload, headers, secret) { const msgId = headers["webhook-id"]; const timestamp = headers["webhook-timestamp"]; const signature = headers["webhook-signature"]; const now = Math.floor(Date.now() / 1000); if (Math.abs(now - parseInt(timestamp)) > 300) { throw new Error("Timestamp too old"); } const signingKey = Buffer.from(secret.replace("whsec_", ""), "base64"); const signedContent = `${msgId}.${timestamp}.${payload}`; const expected = crypto .createHmac("sha256", signingKey) .update(signedContent) .digest("base64"); const received = signature.split(",")[1]; if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) { throw new Error("Invalid signature"); } return JSON.parse(payload); } ``` ご利用の言語向けの[Standard Webhooks](https://www.standardwebhooks.com/)ライブラリを使うこともでき、検証は自動的に行われます。 ## 再試行と配信を扱う [#handle-retries-delivery] webhook では **at-least-once delivery** が使われます。 エンドポイントが 10 秒以内に `2xx` レスポンスを返さない場合、配信は指数バックオフで **最大 10 回** まで再試行されます。 ダッシュボードの webhook 詳細ページから、失敗した配信を手動で再試行できます。 ## 重複するイベントを処理する [#handle-duplicate-events] イベント配信は少なくとも1回は行われるため、エンドポイントで同じイベントを複数回受信することがあります。重複を除外するには、ペイロード内の `id` フィールドを使用してください。 ```js app.post("/webhooks/gt", (req, res) => { const eventId = req.body.id; if (alreadyProcessed(eventId)) { return res.status(200).send("OK"); } // イベントを処理する... markProcessed(eventId); res.status(200).send("OK"); }); ``` ## エンドポイントを管理する [#manage-endpoints] Webhookページでは、次の操作を行えます。 * エンドポイントを削除せずに有効化または無効化する * 購読中のイベントタイプを更新する * 配信履歴を表示する * 各配信試行の詳細を確認する * 失敗した配信を再試行する * signing secret を表示する * エンドポイントを削除する ## ベストプラクティス [#best-practices] * まず `2xx` レスポンスをすぐに返し、その後でイベントを非同期に処理します。 * ペイロードを信頼する前に、`webhook-signature` ヘッダーを検証します。 * 処理済みのイベント ID を保存し、重複するものはスキップします。 * 本番環境の webhook エンドポイントでは HTTPS を使用します。 * 配信履歴を監視し、継続的な失敗がないか確認します。 ## 権限 [#permissions] Webhookエンドポイントを管理するには、`org:webhooks:write` 権限が必要です。 配信履歴を閲覧するには、`org:webhooks:read` 権限が必要です。 デフォルトでは、**Admin** ロールに両方の権限が付与されています。詳しくは、[ロールと権限](/docs/platform/dashboard/reference/roles-and-permissions)を参照してください。