Webhooks
Webhookは、Mottainaiでイベントが発生した際にHTTP POSTでサーバーに通知を送ります。WebSocket接続を維持せずに外部システム(n8n、Zapier、カスタムサービス)と連携できます。
クイックスタート
- 設定 > 開発者 > Webhooks で Webhookを追加 をクリックします。
- HTTPSエンドポイントURLを入力します。
- 受信したいイベントを選択します(空のままにすると全イベントを受信)。
- 署名シークレットをコピーします — 一度しか表示されません。
- テストイベントを送信 をクリックしてエンドポイントを確認します。
CLI(mo 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です。ペイロードは統一されたエンベロープに従います:
{
"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_version | string | 常に "1" |
event | string | イベントタイプ(例:item.created) |
event_id | UUID | このイベントの一意なID |
created_at | ISO 8601 | イベント発生時刻 |
actor_id | UUID | アクションを実行したユーザー(システムイベントでは省略) |
container_id | UUID | 対象コンテナ |
container_type | string | storage、board、calendar など |
data | object | イベント固有のデータ(下記参照) |
batch_id | UUID | 同一バッチ操作のイベントで共有されるID |
イベントタイプ別データ
アイテムイベント(item.created、item.updated、item.deleted):
{
"item_id": "アイテムUUID",
"item_type": "storage",
"title": "オーツミルク",
"category": "乳製品",
"changed_fields": ["quantity"],
"snapshot": { "id": "アイテムUUID", "title": "オーツミルク", "quantity": 3, "..." : "..." }
}changed_fields は item.updated のみに含まれます。snapshot はAPIが返す完全なアイテムデータを含み、item.created と item.updated で提供されます(item.deleted やカード移動などの位置変更では省略されます)。
期限イベント(item.expired、item.expiring_soon):
{
"item_id": "アイテムUUID",
"title": "ヨーグルト",
"expiration_date": "2026-03-11",
"quantity": 2,
"category": "乳製品"
}これらはシステム生成です — actor_id は省略されます。
コンテナイベント(container.created、container.updated、container.deleted):
{
"container_id": "コンテナUUID",
"container_type": "storage",
"name": "冷蔵庫"
}メンバーイベント(member.added、member.removed):
{
"user_id": "ユーザーUUID",
"email": "user@example.com",
"name": "Alice",
"role": "member"
}テストイベント(webhook.test):
{
"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-Type | application/json |
User-Agent | Mottainai-Webhook/1.0 |
X-Mottainai-Event | イベントタイプ(例:item.created) |
X-Mottainai-Delivery | 一意な配信ID(UUID) |
X-Mottainai-Signature-256 | HMAC-SHA256署名(sha256=<hex>) |
署名検証
すべてのペイロードはエンドポイントのシークレットでHMAC-SHA256署名されます。署名を検証して、リクエストがMottainaiから送信され、改ざんされていないことを確認してください。
仕組み
- Mottainaiが
HMAC-SHA256(シークレット, 生のリクエストボディ)を計算します。 - 結果が
X-Mottainai-Signature-256: sha256=<hex-digest>として送信されます。 - サーバーでHMACを再計算し、定数時間比較で照合します。
例(Node.js)
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)
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)
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))
}シークレットのローテーション
ダウンタイムなしでシークレットをローテーション:
POST /api/webhooks/{id}/rotate-secretを呼び出します(または設定でシークレットをローテーションをクリック)。- 新しいシークレットが生成され、一度だけ表示されます。
- 古いシークレットは 1時間 有効です(レスポンスに
expires_old_atが含まれます)。 - 重複期間中は両方のシークレットに対して検証してください。
- 1時間後、古いシークレットは無効になります。
リトライ動作
失敗した配信は指数バックオフで再試行されます:
| 試行 | 失敗後の待ち時間 |
|---|---|
| 1 | 即時 |
| 2 | 1分 |
| 3 | 5分 |
| 4 | 30分 |
| 5 | 2時間 |
| 6 | 8時間 |
| 7 | 24時間 |
| 7回超 | 失敗としてマーク |
リトライの合計期間:約35時間。
サーバーが 2xx ステータスコードを返せば配信成功です。それ以外のステータス、タイムアウト、接続エラーはリトライの対象になります。
失敗した配信
失敗した配信は、設定 > 開発者 > Webhooks > ログ、または POST /api/webhooks/{id}/deliveries/{deliveryId}/retry で手動リトライできます。
自動無効化
エンドポイントが 7日間連続 で失敗・停止した配信のみの場合(成功した配信がない場合)、エンドポイントは自動的に無効化されます。メール通知が届きます。問題を修正した後、設定でエンドポイントを再有効化してください。未送信のイベントは再送されません。
SSRF制限
不正利用を防ぐため、Webhook URLは配信のたびに検証されます:
- HTTPS必須(
http://は開発モードのみ許可) - プライベートIPレンジをブロック:
10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、127.0.0.0/8、169.254.0.0/16、0.0.0.0/8、100.64.0.0/10、::1/128、fc00::/7 - 配信ごとにDNS再解決(DNSリバインディング攻撃の防止)
URLがブロックされたIPに解決された場合、配信は即座に失敗し、明確なエラーメッセージが表示されます。
CLI
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_IDMCPツール
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日間の連続失敗 |