メインコンテンツへスキップ

Webhookエンドポイントとは?

Webhookエンドポイント(endpoint)は、ノート・要約・ドキュメントが変わった瞬間に Tiro が HTTP POST リクエストを送信する、お客様のアプリケーションの URL です。Tiro Platformにエンドポイントを一度登録しておくと、Tiro がすべてのイベントをリアルタイムでその宛先へ送信します。API をポーリングする必要はありません。

Webhook とは

Webhook を利用しますと、ワークスペースでイベントが発生した際に、アプリケーションがリアルタイムで通知を受け取れます。API をポーリングする代わりに、何かが起こるたびに Tiro がお客様の endpoint へ HTTP POST リクエストを送信します。

Webhook の仕組み

  1. 設定する: アプリケーションに webhook の endpoint を用意します
  2. 登録する: 受け取りたいイベントを処理するために、endpoint を追加します
  3. 受信する: イベントが発生すると即座に通知が届きます
  4. 処理する: アプリケーションでイベントのデータを処理します

イベントとリソースの構造

Webhook は イベント(Events)リソース(Resources) を中心に構成されています。

イベント

イベントは、ワークスペースで発生したアクションを表します。詳しくは Note EventsNote Document EventsNote Summary EventsFolderNote Events をご覧ください。 Webhook イベントは metadata のみを含めることで、サイズが数百 KB 未満に収まるよう設計されています。transcript や script などの大きなコンテンツは、webhook の信頼性とパフォーマンスを確保するため、別の API を通じてアクセスします。

リソース

リソースは、イベントの対象となる主要なエンティティを表します。現在サポートしているのは以下のとおりです。
  • Note: 個々のノートのリソース
  • NoteDocument: ノートから生成されるテンプレートベースのドキュメント
  • NoteSummary: ノートに対して AI が生成する要約
  • FolderNoteRelation: フォルダとノートの関係

Webhook の payload 構造

すべての webhook イベントは、標準の Event Structure 構造に従います。

セキュリティ

Webhook リクエストは、Authorization ヘッダーに設定された secret key で認証されます。
Authorization: Bearer {secret_key}
secret key は webhook の endpoint を設定する際に発行され、受信するリクエストの真正性を検証するために使用します。設定済みの secret と受信した secret を比較するだけで、真正性を確認できます。

検証の例

function verifyWebhookAuth(authHeader, expectedSecret) {
  const token = authHeader.replace('Bearer ', '');
  return token === expectedSecret;
}

// Usage in your webhook endpoint
app.post('/ja/developers/webhooks/tiro', (req, res) => {
  const authHeader = req.headers.authorization;
  
  if (!verifyWebhookAuth(authHeader, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Unauthorized');
  }
  
  // Process webhook event
  const event = req.body;
  handleWebhookEvent(event);
  
  res.status(200).send('OK');
});
import os
from flask import Flask, request

def verify_webhook_auth(auth_header, expected_secret):
    if not auth_header or not auth_header.startswith('Bearer '):
        return False
    token = auth_header.replace('Bearer ', '')
    return token == expected_secret

# Usage in your webhook endpoint
@app.route('/ja/developers/webhooks/tiro', methods=['POST'])
def handle_webhook():
    auth_header = request.headers.get('Authorization')
    
    if not verify_webhook_auth(auth_header, os.getenv('WEBHOOK_SECRET')):
        return 'Unauthorized', 401
    
    # Process webhook event
    event = request.get_json()
    handle_webhook_event(event)
    
    return 'OK', 200
import (
    "encoding/json"
    "net/http"
    "os"
    "strings"
)

func verifyWebhookAuth(authHeader, expectedSecret string) bool {
    if !strings.HasPrefix(authHeader, "Bearer ") {
        return false
    }
    token := strings.TrimPrefix(authHeader, "Bearer ")
    return token == expectedSecret
}

// Usage in your webhook endpoint
func handleWebhook(w http.ResponseWriter, r *http.Request) {
    authHeader := r.Header.Get("Authorization")
    
    if !verifyWebhookAuth(authHeader, os.Getenv("WEBHOOK_SECRET")) {
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }
    
    // Process webhook event
    var event WebhookEvent
    json.NewDecoder(r.Body).Decode(&event)
    handleWebhookEvent(event)
    
    w.WriteHeader(http.StatusOK)
}
@RestController
class WebhookController {
    
    @Value("\${webhook.secret}")
    private lateinit var webhookSecret: String
    
    private fun verifyWebhookAuth(authHeader: String?, expectedSecret: String): Boolean {
        if (authHeader?.startsWith("Bearer ") != true) {
            return false
        }
        val token = authHeader.removePrefix("Bearer ")
        return token == expectedSecret
    }
    
    // Usage in your webhook endpoint
    @PostMapping("/ja/developers/webhooks/tiro")
    fun handleWebhook(
        @RequestHeader("Authorization") authHeader: String?,
        @RequestBody event: WebhookEvent
    ): ResponseEntity<String> {
        if (!verifyWebhookAuth(authHeader, webhookSecret)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized")
        }
        
        // Process webhook event
        handleWebhookEvent(event)
        return ResponseEntity.ok("OK")
    }
}

配信とリトライ

  • Method: HTTP POST
  • Content-Type: application/json
  • Timeout: 60 秒
  • Retries: 最大 5 回のリトライ(合計 6 回の試行)。exponential backoff で実施します
  • Success: 2xx の HTTP ステータスコードであれば成功とみなします

リトライのスケジュール

  1. 15 秒後
  2. 30 秒後
  3. 5 分後
  4. 30 分後
  5. 2 時間後

はじめに

  1. endpoint を用意する: POST リクエストを受信できる HTTP の endpoint を作成します
  2. webhook を設定する: Tiro Platform で webhook の endpoint を登録します
  3. イベントを処理する: アプリケーションで受信した webhook の payload を処理します
webhook の endpoint は Tiro Platform で設定できます。
Webhook の endpoint はワークスペース単位で登録・管理されます。Platform で endpoint を追加すると、現在のワークスペースのイベントがその endpoint に配信されます。複数のワークスペースを運用する場合は、ワークスペースごとに endpoint を登録し、payload 最上位の workspaceGuid でイベントの送信元ワークスペースを判別してください。
endpoint を登録したら、受信する Event Structure を把握しましょう。そのうえで、本番運用を始める前に Best Practices を読み、リトライ・冪等性・署名検証への対応をご確認ください。