Skip to content
Als Markdown

Webhooks

Webhookは、Mottainaiでイベントが発生した際にHTTP POSTでサーバーに通知を送ります。WebSocket接続を維持せずに外部システム(n8n、Zapier、カスタムサービス)と連携できます。

クイックスタート

  1. 設定 > 開発者 > WebhooksWebhookを追加 をクリックします。
  2. HTTPSエンドポイントURLを入力します。
  3. 受信したいイベントを選択します(空のままにすると全イベントを受信)。
  4. 署名シークレットをコピーします — 一度しか表示されません。
  5. テストイベントを送信 をクリックしてエンドポイントを確認します。

CLImo webhooks)や API でもWebhookを管理できます。

イベントタイプ

イベントトリガー
item.createdアイテムがコンテナに追加された
item.updatedアイテムのフィールドが変更された(changed_fieldsを含む)
item.deletedアイテムが削除された
item.expired在庫アイテムの有効期限が今日
item.expiring_soon在庫アイテムが7日以内に期限切れ(期限超過アイテムは10日ごとに通知)
container.created新しいコンテナが作成された
container.updatedコンテナの名前や設定が変更された
container.deletedコンテナが削除された
member.addedユーザーがコンテナに追加された
member.removedユーザーがコンテナから削除された
webhook.testテストボタンから手動送信(キューを経由しない)

エンドポイント設定で特定のイベントを選択するか、リストを空にしてすべてのイベントを受信します。

ペイロード形式

すべてのWebhook配信は Content-Type: application/json のHTTP POSTです。ペイロードは統一されたエンベロープに従います:

json
{
  "schema_version": "1",
  "event": "item.created",
  "event_id": "a1b2c3d4-...",
  "created_at": "2026-03-11T10:30:00Z",
  "actor_id": "ユーザーUUID",
  "container_id": "コンテナUUID",
  "container_type": "storage",
  "data": { ... },
  "batch_id": null
}

フィールド

フィールド説明
schema_versionstring常に "1"
eventstringイベントタイプ(例:item.created
event_idUUIDこのイベントの一意なID
created_atISO 8601イベント発生時刻
actor_idUUIDアクションを実行したユーザー(システムイベントでは省略)
container_idUUID対象コンテナ
container_typestringstorageboardcalendar など
dataobjectイベント固有のデータ(下記参照)
batch_idUUID同一バッチ操作のイベントで共有されるID

イベントタイプ別データ

アイテムイベントitem.createditem.updateditem.deleted):

json
{
  "item_id": "アイテムUUID",
  "item_type": "storage",
  "title": "オーツミルク",
  "category": "乳製品",
  "changed_fields": ["quantity"],
  "snapshot": { "id": "アイテムUUID", "title": "オーツミルク", "quantity": 3, "..." : "..." }
}

changed_fieldsitem.updated のみに含まれます。snapshot はAPIが返す完全なアイテムデータを含み、item.createditem.updated で提供されます(item.deleted やカード移動などの位置変更では省略されます)。

期限イベントitem.expireditem.expiring_soon):

json
{
  "item_id": "アイテムUUID",
  "title": "ヨーグルト",
  "expiration_date": "2026-03-11",
  "quantity": 2,
  "category": "乳製品"
}

これらはシステム生成です — actor_id は省略されます。

コンテナイベントcontainer.createdcontainer.updatedcontainer.deleted):

json
{
  "container_id": "コンテナUUID",
  "container_type": "storage",
  "name": "冷蔵庫"
}

メンバーイベントmember.addedmember.removed):

json
{
  "user_id": "ユーザーUUID",
  "email": "user@example.com",
  "name": "Alice",
  "role": "member"
}

テストイベントwebhook.test):

json
{
  "schema_version": "1",
  "event": "webhook.test",
  "event_id": "...",
  "created_at": "2026-03-11T10:30:00Z",
  "actor_id": "ユーザーUUID",
  "message": "This is a test event from Mottainai."
}

HTTPヘッダー

すべての配信に以下のヘッダーが含まれます:

ヘッダー説明
Content-Typeapplication/json
User-AgentMottainai-Webhook/1.0
X-Mottainai-Eventイベントタイプ(例:item.created
X-Mottainai-Delivery一意な配信ID(UUID)
X-Mottainai-Signature-256HMAC-SHA256署名(sha256=<hex>

署名検証

すべてのペイロードはエンドポイントのシークレットでHMAC-SHA256署名されます。署名を検証して、リクエストがMottainaiから送信され、改ざんされていないことを確認してください。

仕組み

  1. Mottainaiが HMAC-SHA256(シークレット, 生のリクエストボディ) を計算します。
  2. 結果が X-Mottainai-Signature-256: sha256=<hex-digest> として送信されます。
  3. サーバーでHMACを再計算し、定数時間比較で照合します。

例(Node.js)

javascript
import crypto from 'node:crypto';

function verifySignature(secret, payload, signature) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// ハンドラー内:
const signature = req.headers['x-mottainai-signature-256'];
const body = await getRawBody(req); // パース前の生バイト
if (!verifySignature(process.env.WEBHOOK_SECRET, body, signature)) {
  return res.status(401).send('Invalid signature');
}

例(Python)

python
import hashlib
import hmac

def verify_signature(secret: str, payload: bytes, signature: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

例(Go)

go
func verifySignature(secret string, payload []byte, signature string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(signature))
}

シークレットのローテーション

ダウンタイムなしでシークレットをローテーション:

  1. POST /api/webhooks/{id}/rotate-secret を呼び出します(または設定でシークレットをローテーションをクリック)。
  2. 新しいシークレットが生成され、一度だけ表示されます。
  3. 古いシークレットは 1時間 有効です(レスポンスに expires_old_at が含まれます)。
  4. 重複期間中は両方のシークレットに対して検証してください。
  5. 1時間後、古いシークレットは無効になります。

リトライ動作

失敗した配信は指数バックオフで再試行されます:

試行失敗後の待ち時間
1即時
21分
35分
430分
52時間
68時間
724時間
7回超失敗としてマーク

リトライの合計期間:約35時間。

サーバーが 2xx ステータスコードを返せば配信成功です。それ以外のステータス、タイムアウト、接続エラーはリトライの対象になります。

失敗した配信

失敗した配信は、設定 > 開発者 > Webhooks > ログ、または POST /api/webhooks/{id}/deliveries/{deliveryId}/retry で手動リトライできます。

自動無効化

エンドポイントが 7日間連続 で失敗・停止した配信のみの場合(成功した配信がない場合)、エンドポイントは自動的に無効化されます。メール通知が届きます。問題を修正した後、設定でエンドポイントを再有効化してください。未送信のイベントは再送されません。

SSRF制限

不正利用を防ぐため、Webhook URLは配信のたびに検証されます:

  • HTTPS必須http:// は開発モードのみ許可)
  • プライベートIPレンジをブロック10.0.0.0/8172.16.0.0/12192.168.0.0/16127.0.0.0/8169.254.0.0/160.0.0.0/8100.64.0.0/10::1/128fc00::/7
  • 配信ごとにDNS再解決(DNSリバインディング攻撃の防止)

URLがブロックされたIPに解決された場合、配信は即座に失敗し、明確なエラーメッセージが表示されます。

CLI

bash
mo webhooks list
mo webhooks create --url "https://example.com/hook" [--events item.created,item.updated] [--containers ID1,ID2] [--description "My hook"]
mo webhooks get WEBHOOK_ID
mo webhooks update WEBHOOK_ID --url "https://example.com/new-hook" [--events item.created]
mo webhooks delete WEBHOOK_ID --yes
mo webhooks test WEBHOOK_ID
mo webhooks rotate-secret WEBHOOK_ID
mo webhooks deliveries WEBHOOK_ID
mo webhooks retry WEBHOOK_ID DELIVERY_ID
mo webhooks enable WEBHOOK_ID

MCPツール

  • list_webhooks — すべてのWebhookエンドポイントを一覧表示
  • create_webhook — 新しいエンドポイントを登録
  • get_webhook — エンドポイントの詳細を取得
  • update_webhook — URL、イベント、ステータスを更新
  • delete_webhook — エンドポイントを削除
  • test_webhook — エンドポイントにテストイベントを送信
  • rotate_webhook_secret — 署名シークレットをローテーション(1時間の重複期間あり)
  • list_webhook_deliveries — エンドポイントの配信履歴を一覧表示
  • retry_webhook_delivery — 失敗した配信を再試行

制限

制限
ユーザーあたりの最大エンドポイント数5
最大リトライ回数7(約35時間)
配信タイムアウト10秒
キャプチャされるレスポンスボディ4096文字
自動無効化までの期間7日間の連続失敗