watch 通知
events.watch と calendarList.watch の Webhook チャネルを登録し、ローカルの HTTP コールバックで通知を受け取り、テストで配信をドレインする方法を説明します。
Calendarpit は events.watch と calendarList.watch のプッシュ通知に対応しています。公開 URL や HTTPS トンネルを用意しなくても、Webhook の処理をローカルでテストできます。監視しているカレンダーのイベントやユーザーのカレンダーリストが変わると、Calendarpit はコールバックアドレスに HTTP POST リクエストを送ります。
チャネルの登録と停止
カレンダーのイベントを監視するには、POST /calendar/v3/calendars/{calendarId}/events/watch を送ります。リクエストは次の条件を満たす必要があります。
- スコープ: OAuth アクセストークンに、イベントを読み取れるスコープ(
calendar、calendar.readonly、calendar.events、calendar.events.readonlyのいずれか)が必要です。ほかのスコープでは403になります。 - カレンダー: 対象は認可したユーザーのカレンダーに限られます。
primaryも使えます。それ以外のカレンダーを指定すると404 notFoundになります。
ユーザーのカレンダーリストを監視する場合は、カレンダーリストを参照してください。
curl -X POST http://127.0.0.1:4173/calendar/v3/calendars/primary/events/watch \
-H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" \
-d '{
"id": "channel-1",
"type": "web_hook",
"address": "http://127.0.0.1:3000/webhooks/calendar",
"token": "secret-123"
}'リクエストボディ
リクエストボディに書けるフィールドは次のとおりです。
id(必須): ASCII の英字・数字・アンダースコア・ハイフンで 1〜64 文字(^[A-Za-z0-9_-]{1,64}$)。ワークスペース内の有効なイベントチャネルとカレンダーリストチャネルすべてで一意である必要があります。重複すると409 duplicateになります。type(必須):"web_hook"を指定します。address(必須): コールバック URL です(http:またはhttps:)。token(任意): 空ではなく空白だけでもない、256 文字以内の文字列です。CR と LF は使えません。X-Goog-Channel-Tokenにそのまま入れて送られます。expiration(任意): 未来の時刻を表すミリ秒単位の Unix タイムスタンプです。数値でも数字の文字列でも指定できます。- 省略すると作成から 24 時間後になります。
- 上限は 7 日です。7 日を超える値はエラーにならず、上限に切り詰められます。実際の値はレスポンスで確認してください。
エラーになる場合:
- 値が不正な場合は
400 invalidParameterになります。 - 上記以外のフィールド(
params、params.ttl、payloadなど)は400 unsupportedFeatureになります。未知のクエリパラメータも同様です。
レスポンス
events.watch が成功すると 200 が返り、次の値が含まれます。
kind: "api#channel"idresourceId:calendar-<calendarId>resourceUri:<origin>/calendar/v3/calendars/<calendarId>/events(<calendarId>は URL エンコードされます)expiration: ミリ秒の文字列token: 指定した場合のみ
primary で登録した場合でも、<calendarId> には監視対象カレンダーの実際の ID が入ります。返された resourceId は channels.stop で使います。
登録済みのチャネルをそのまま延長することはできません。延長したいときは新しいチャネルを登録してください。新しい id を使うか、古いチャネルを停止してから同じ id を使います。
チャネルの停止
チャネルを停止するには、id と resourceId だけを含む JSON ボディを POST /calendar/v3/channels/stop に送ります。
- ボディ: ほかのフィールドがあると
400 unsupportedFeatureになります。 - トークン: チャネル作成時と同じ OAuth 接続(grant)のトークンを使います。イベントかカレンダーリストを読み取れるスコープが必要です。
- 成功時:
204が返り、待機中や送信中の配信は中止されます。 - 見つからない場合:
idが存在しない、resourceIdが一致しない、別の接続のトークンを使った、のいずれかの場合は404 notFoundになります。
期限切れだけでは 404 にはなりません。同じ接続と一致する resourceId で停止する場合、期限切れでもまだ削除されていないチャネルには 204 が返ります。削除された後は、その id は見つかりません。
コールバックに届く内容
Calendarpit は watch のレスポンスを返す前に、最初の sync 通知(メッセージ番号 1)を配信待ちに入れます。そのため、呼び出し側が 200 レスポンスを受け取る前にコールバックが届くことがあります。
events.watch では、その後イベントの書き込みが確定するたびに、影響を受けたすべてのカレンダーの watch チャネルへ exists 通知を送ります。
- 書き込みの種類: insert・update・patch・delete。
- 書き込み元: Calendar API・管理 API・管理画面。
- 含まれるもの: 参加者側のコピーや、主催者側に反映される出欠の更新。
- 含まれないもの: ユーザーやカレンダーのメタデータの編集。イベントチャネルには通知されません。
配信はどれも address への HTTP POST で、ボディは空です。Calendarpit はリソースの内容、X-Goog-Changed、OAuth アクセストークンをコールバックに送りません。イベントの通知を受け取ったら、保存してある syncToken を付けて events.list を呼び出し、変更を取得してください(差分同期を参照)。
| ヘッダー | 値 |
|---|---|
X-Goog-Channel-ID | 登録時のチャネル id |
X-Goog-Resource-ID | リソースの識別子(calendar-<calendarId> または calendarList-<userId>) |
X-Goog-Resource-URI | 監視対象カレンダーのイベント、またはユーザーのカレンダーリストのエンドポイント URL |
X-Goog-Resource-State | チャネル作成時は sync、監視対象が変わったときは exists |
X-Goog-Message-Number | 1 から始まり、通知ごとに 1 ずつ増える整数(再試行では同じ番号を使います) |
X-Goog-Channel-Expiration | 有効期限(RFC 1123 形式、UTC) |
X-Goog-Channel-Token | チャネルの token。登録時に指定した場合のみ付きます |
カレンダーリスト
ユーザーのカレンダーリストを監視するには、POST /calendar/v3/users/me/calendarList/watch を送ります。カレンダーリストを読み取れるスコープ(calendar、calendar.readonly、calendar.calendarlist、calendar.calendarlist.readonly のいずれか)が必要です。ほかのスコープでは 403 になります。
次の点は events.watch と同じルールです。
- リクエストボディのフィールド
- 共通のチャネル ID 空間
- 有効期限の制限
- コールバックホストの許可リスト
- ヘッダー
- 再試行のルール
- 配信ログ
channels.stop
params フィールド(params.ttl を含む)は 400 unsupportedFeature になります。システム用以外のクエリパラメータも同様です。有効期限は expiration で指定してください。
成功すると 200 が返り、resourceId: "calendarList-<userId>" と resourceUri: "<origin>/calendar/v3/users/me/calendarList" が含まれます。チャネルを停止するには、作成時と同じ OAuth 接続のトークンで POST /calendar/v3/channels/stop を送ります。
カレンダーリストの通知
このチャネルでも最初に sync 通知(1)が配信待ちに入ります。その後、次の場合に exists が送られます。
- カレンダーリストへの書き込み(
insert、patch、update、delete)で、ユーザーの保存されているリスト設定が変わったとき。 - 管理 API または管理画面で、そのユーザー自身のカレンダーが作成・更新・削除されたとき。更新は管理 API の
PATCHすべてが対象で、何も変わらない場合も含みます。
次の変更は calendarList.watch チャネルに通知されません。
- 何も変更しないカレンダーリストへの書き込み
- イベントの書き込み
- 別のユーザーのカレンダーの変更
- ユーザーのメタデータの編集
また、カレンダーリストへの書き込みでは events.watch チャネルに通知されません。
カレンダーリストは syncToken に対応していません。コールバックを受け取ったら、calendarList.list で取得し直してください。
許可されるコールバックホスト
Google Calendar と違い、Calendarpit では http: のコールバックを使えます。デフォルトで許可されるホストは 127.0.0.1、[::1]、localhost だけです。localhost への配信は 127.0.0.1 に送られます。
ほかのコンテナやテスト用ホストを許可するには、CALENDARPIT_WATCH_HOSTS にホスト名をカンマ区切りで設定します。例: CALENDARPIT_WATCH_HOSTS=app,host.docker.internal
- 各エントリはコールバック URL のホスト名と完全に一致する必要があります。
- 小文字で書きます。ポートは付けず、ワイルドカードも使えません。
- IPv6 は角かっこ付きの省略形で書きます(例:
[fd00::1])。
次のいずれかに当てはまるコールバック URL は 400 で拒否されます。
- HTTP(S) 以外のスキーム
- 認証情報を含む
- 空でない
#fragmentを持つ - 許可されていないホスト
許可リストは、登録時、各配信の試行前、起動時のチャネル復元時に確認されます。
配信ルールとチャネルの終了
| 項目 | 値 |
|---|---|
| 1 通知あたりの試行回数 | 最大 3 回(初回 + 再試行 2 回) |
| 1 回あたりのタイムアウト | 2000 ms |
| 試行の間隔 | 100 ms |
| 成功とみなすステータスコード | 200、201、202、204(リダイレクトはたどらず、失敗として扱います) |
| 順序 | チャネルごとに 1 件ずつ順番に配信 |
| 配信ログ | 最新 200 件(queued、delivered、failed、cancelled) |
チャネルが停止する場合
次のいずれかが起きると、チャネルは停止します。待機中や送信中の配信は cancelled になります。
channels.stopを呼び出したとき- チャネルの有効期限が切れたとき
- 対象の OAuth 接続が取り消されたとき
- 対象のユーザーが削除されたとき
関連するケース:
- カレンダーの削除: そのカレンダーの
events.watchチャネルは停止します。所有者のcalendarList.watchチャネルは有効なままで、existsを受け取ります。 - リセットと Fixture の読み込み:
POST /__calendarpit/resetまたはPOST /__calendarpit/fixturesを呼び出すと、すべてのチャネルが停止し、配信ログも消去されます(テスト制御 APIを参照)。 - 期限切れのチャネル: すぐには削除されません。チャネルの登録時、イベントやカレンダーリストの通知処理時、状態の読み取り時に削除されます。削除されたチャネルの
idは再利用できます。期限切れのチャネルへの配信はキャンセルされます。
再起動時の扱い
Calendarpit は、再起動をまたいで配信待ちを保持するキューを持っていません。
- SQLite モード: 期限切れでないイベントチャネルとカレンダーリストチャネルは、再起動後もメッセージ番号を引き継いで再開します。ただし、OAuth 接続が有効で、コールバックホストが許可リストに含まれている場合に限ります。
- 中断された配信: シャットダウンで中断された配信は
cancelledになり、再送されません。 - 停止中の変更: サーバーが停止している間の変更については、通知は送られません。
:memory:モード: 再起動するとすべてのチャネルがなくなります(ストレージと再起動を参照)。
テストでの配信の確認とドレイン
管理画面には最新 200 件の配信記録が表示されます。各記録で試行回数、状態、最後に受け取った HTTP ステータスコードを確認できます。
自動テストでは、Content-Type: application/json を付けて POST /__calendarpit/deliveries/drain を呼び出します。この時点で待機中の配信とその再試行がすべて終わるまで待ってから応答が返ります。
curl -X POST http://127.0.0.1:4173/__calendarpit/deliveries/drain \
-H "Content-Type: application/json" \
-d '{}'レスポンスは 200 で、配信ログ全体が配列で返ります。ドレインの呼び出しを始めた後に配信待ちに入った配信は、待機の対象になりません。