Skip to content
言語: 日本語

開発者向けAPI

Webhook:イベント、ペイロード、署名

2026年11月1日更新 約9分で読めます

ダッシュボードでWebhookを設定し、配信を受け取り、HMAC署名を検証する方法を、curl、Node.js、PHPの例とともに説明します。

Webhookは、ワークスペースで何かが起きるとすぐに(たとえばページが公開されたとき)、サーバーにHTTPリクエストを送ります。これにより、何か変わったかを繰り返し問い合わせる必要がなくなります。すべての配信には署名が付いているため、サーバーは本物のメッセージと偽造されたものを見分けられます。

必要なもの

  • ワークスペースのオーナーまたは管理者。「Webhooks」のメニュー項目はこれらのロールにだけ表示されます
  • Creator Pro以上。Creator Freeでは「WebhookはCreator Pro以上で利用できます。」と表示されます
  • WebhookはCreator Proで最大5個、Creator Agencyで25個、Enterpriseで100個(調整可能)
  • 公開されたhttpsアドレスでアクセスできるサーバー

Webhookはダッシュボードでのみ管理します。APIキーで作成や変更をすることはできません。

Webhookを作成する

Webhookがない状態の「Webhooks」ページと「+ 新しいWebhook」ボタン
ワークスペースの「Webhooks」ページ
  1. ナビゲーションの「Webhooks」を開きます
  2. 「+ 新しいWebhook」をクリックします
  3. 送信先URLを入力し、イベントを選びます
  4. 任意:説明を入力し、ステータスを選びます
  5. 「Webhookを作成」をクリックします
  6. 「完了」ステップで署名シークレット(「署名シークレット(今だけ表示)」)をコピーし、サーバーに保管します。表示されるのはこのときだけです
  7. 「テスト」でWebhookを試します。結果は次のテストまでカードに残ります:「配信に成功しました」または「配信されませんでした」とHTTPコード(または「応答なし」)、所要時間、レスポンスの冒頭
「送信先URL(https://…)」
最大2048文字です。httpsのみで、URLに認証情報を含めたり、localhostのアドレスや.local、.internal、.lanなどの内部名を使ったりすることはできません。名前は公開IPアドレスを指している必要があり、LinkHarborは配信のたびにこれを再確認します。
「イベント」
少なくとも1つのイベントです。各グループに「すべて選択」と「すべての選択を解除」があります。
「説明」
任意で、最大255文字です(例:Webhookの用途)。
「ステータス」
「有効」または「無効」です。無効なWebhookには配信されません。

イベント

イベントダッシュボードでの表示タイミングdataの内容
page.publishedページ公開ダッシュボード、API、AIアシスタントのいずれかでページが公開されたときpage_id、version、checksum、published_by_user_id(APIキーとAIアシスタントの場合はnull)
form.submittedフォーム送信訪問者がいずれかのページでフォームを送信したときform_id、form_name、submission_id、page_id、block_id、submitted_at、values(フィールドのキー → 値)、consent(同意のキー → true/false)
domain.verifiedドメイン検証独自ドメインの検証に成功したときdomain_id、hostname、verification_method、status
domain.disabledドメイン無効化ダッシュボードで独自ドメインが無効化されたとき(ダウングレード時は除く)domain_id、hostname、status
webhook.test–(「テスト」ボタン)「テスト」をクリックしたときmessage(テストを開始した人のアカウント言語での注記。これで照合しないでください)

form.submittedには、訪問者が送信したとおりの値が含まれますが、IPアドレスやブラウザの情報は含まれません。配信はバックグラウンドで行われるため、訪問者は待つ必要がなく、配信に失敗しても送信は妨げられません。

配信の構造

LinkHarborは、すべての配信をJSON本文と次のヘッダー付きのPOSTとして送信します。

ヘッダー内容
Content-Typeapplication/json
X-LinkHarbor-Eventイベントの名前(例:page.published)
X-LinkHarbor-Delivery-Idこの配信の一意のID(UUID)。本文のidと同じです
X-LinkHarbor-Timestamp送信時刻(Unix秒)
X-LinkHarbor-Signaturesha256=に続く16進数のHMAC-SHA256署名
X-LinkHarbor-Signature-Versionv1
page.publishedのペイロードの例
{
  "id": "3f1c2a9e-8d4b-4a51-9f0e-6b7c1d2e3f40",
  "event": "page.published",
  "created_at": "2026-11-01T09:30:12+00:00",
  "data": {
    "page_id": 42,
    "version": 8,
    "checksum": "9b1e5c…",
    "published_by_user_id": 7
  }
}

再配信には新しいidと、redelivery_of(繰り返している配信のID)とdelivery_root(このチェーンの最初の配信のID)のフィールドが付きます。

署名を検証する

各Webhookには独自の署名シークレット(16進数32文字)があります。表示されるのはちょうど一度だけで、Webhookを作成するときの最後のステップと、「シークレット ↺」の直後です。すぐにサーバーにコピーしてください。LinkHarborは後で再表示しません。LinkHarborはこれを使って、次のように署名を計算します。

  1. メッセージを組み立てます:タイムスタンプ、ドット、配信ID、ドット、生の本文、つまりtimestamp.delivery_id.body
  2. 署名シークレットをキーとして、このメッセージのHMAC-SHA256を計算します
  3. 結果を接頭辞sha256=付きの16進数文字列として送信します

サーバーでは、一定時間比較で署名を確認し、タイムスタンプが300秒より古い配信を拒否し、重複を見分けるために配信IDを少なくともその間保存してください。重要:署名されるのは、届いたままの生の本文です。自分でエンコードし直したJSONではありません。

Node.js(Express):Webhookを受信して検証する
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const SECRET = process.env.LH_WEBHOOK_SECRET;
const seen = new Set(); // 本番環境では: 有効期限付きのデータベースまたはRedis

app.post('/webhooks/linkharbor', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('X-LinkHarbor-Timestamp') || '';
  const id = req.get('X-LinkHarbor-Delivery-Id') || '';
  const sig = req.get('X-LinkHarbor-Signature') || '';

  if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
    return res.status(400).send('timestamp');
  }

  const expected = 'sha256=' + crypto
    .createHmac('sha256', SECRET)
    .update(ts + '.' + id + '.')
    .update(req.body) // Bufferとしての生の本文
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(sig);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).send('signature');
  }

  const event = JSON.parse(req.body.toString('utf8'));
  const key = event.delivery_root || event.id; // 同じチェーンの再配信を見分ける
  if (!seen.has(key)) {
    seen.add(key);
    // ここで処理する。理想的にはキューで非同期に
  }
  res.sendStatus(200);
});

app.listen(3000);
PHP:Webhookを受信して検証する
<?php
$secret = getenv('LH_WEBHOOK_SECRET');
$body   = file_get_contents('php://input');
$ts     = $_SERVER['HTTP_X_LINKHARBOR_TIMESTAMP'] ?? '';
$id     = $_SERVER['HTTP_X_LINKHARBOR_DELIVERY_ID'] ?? '';
$sig    = $_SERVER['HTTP_X_LINKHARBOR_SIGNATURE'] ?? '';

if (!ctype_digit($ts) || abs(time() - (int)$ts) > 300) {
    http_response_code(400);
    exit('timestamp');
}

$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $id . '.' . $body, $secret);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit('signature');
}

$event = json_decode($body, true);
// $event['delivery_root'] ?? $event['id'] で重複した配信を見分けてから、
// 処理する。
http_response_code(200);

curlでエンドポイントをテストする

このスクリプトは、正しく署名されたテスト配信を自分のサーバーに送信します。LinkHarborでWebhookを作成する前に便利です。

署名付きのテスト配信をシミュレートする
SECRET="your-signing-secret"
URL="https://example.com/webhooks/linkharbor"
TS=$(date +%s)
ID=$(uuidgen | tr 'A-Z' 'a-z')
BODY='{"id":"'"$ID"'","event":"webhook.test","created_at":"2026-11-01T09:30:12+00:00","data":{"message":"これはこのWebhookのテストイベントです。"}}'
SIG="sha256=$(printf '%s' "$TS.$ID.$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')"

curl -X POST "$URL" \
  -H "Content-Type: application/json" \
  -H "X-LinkHarbor-Event: webhook.test" \
  -H "X-LinkHarbor-Delivery-Id: $ID" \
  -H "X-LinkHarbor-Timestamp: $TS" \
  -H "X-LinkHarbor-Signature: $SIG" \
  -H "X-LinkHarbor-Signature-Version: v1" \
  -d "$BODY"

レスポンス、タイムアウト、再配信

  • 2xxのステータスのレスポンスはすべて成功とみなされます。リダイレクト(3xx)はたどりません
  • サーバーが応答するまでの時間は10秒、接続を確立するまでの時間は最大5秒です
  • 1つのイベントのすべてのWebhookで合計25秒を共有し、それまでに順番が来なかったものは失敗としてマークされ、再配信できます
  • LinkHarborは失敗した配信を自動では再試行しません
  • 失敗した配信は手動で再送信できますが、その後成功したものは再送信できません。再配信には新しいidと新しいcreated_atが付きます
  • 1つのイベントにつき、最初の配信を含めて最大3回まで試行できます。その後は「配信失敗メッセージ」に入り、再送信できなくなります

ダッシュボードでの配信

「配信」でWebhookのログが開きます。3つのタブがあります。

「すべての配信」
すべての配信で、イベント、ステータス(「成功 (2xx)」「クライアントエラー (4xx)」「サーバーエラー (5xx)」「失敗」)、「上限」で絞り込めます。「↺ 再試行」は、その後に成功した配信がない限り、失敗した配信を再び配信します。行をクリックすると、リクエスト、レスポンス、本文、生のJSONが表示されます。
「再試行」
再配信のチェーンが、「復旧済み」または「引き続き失敗」の結果と試行回数とともに表示されます。
「配信失敗メッセージ」
許可されたすべての試行(最初の配信と再配信を合わせて)が失敗した配信です。このタブは閲覧専用で、再配信はできません。エンドポイントを確認してください。新しいイベントは通常どおり配信されます。データが必要な場合はAPIで取得してください。

再配信が行われない場合、ダッシュボードに理由が表示されます。

「再送信されませんでした:このイベントはすでに … 回失敗しており、これ以上試行できません。『配信失敗メッセージ』に表示されます。」
3回の試行をすべて使い切りました。
「再送信されませんでした:このイベントはその後すでに正常に配信されています。」
同じチェーンのその後の配信がすでに成功しています。

ダッシュボードでの検証

「検証」には2つの補助機能があります。

「検証キット」
「キットを読み込む」で、アルゴリズム、ヘッダー名、署名するメッセージの形式、推奨される許容時間300秒が表示されます。シークレット自体は含まれません。
「署名を検証」
受け取った配信の配信ID、タイムスタンプ、署名、生のリクエスト本文を入力します。LinkHarborが「署名は有効です」または「署名が無効です」と知らせます。タイムスタンプと署名は、X-LinkHarbor-TimestampとX-LinkHarbor-Signatureヘッダーからコピーしてください。チェックは直近5分間の配信にのみ機能し、それより古いものでは署名が正しくても「署名が無効です」と表示されます。

シークレットのローテーション、無効化、削除

  • 「シークレット ↺」は新しい署名シークレットを生成して一度だけ表示します。古いものはすぐに使えなくなり、サーバーは新しいものを使う必要があります
  • 「編集」でステータスを「無効」にすると、配信を一時停止できます
  • ごみ箱アイコンでWebhookを削除します。メッセージを閉じるまでは「元に戻す」で取り消せます

小さいプランに移行すると、新しい上限を超えたWebhookは無効になり、Creator Freeではすべて無効になります。Webhookは保持され、アップグレード後、LinkHarborは無効になったWebhookを新しいプランの上限まで自動的に再び有効にします。自分で無効にしたものも含まれます。

答えが見つかりませんか?

お困りのことをお知らせください。サポートチームが個別にお手伝いします。

サポートに問い合わせる