文字起こしのWebhook vs ポーリング:非同期処理の選択
APIWebhook開発者

文字起こしのWebhook vs ポーリング:非同期処理の選択

BMMamane B. MoussaMay 26, 2026Updated July 2, 20263 min read

Summarize this article with:

TL;DR

1日50件未満のジョブや、ユーザーが進行状況バーを見ている場合はポーリングを選びましょう。1日約100件を超えたとき、ジョブがバックグラウンドで実行されるとき、結果を複数の宛先へ振り分けるときはWebhookに切り替えましょう。数字は明快です。1日200件では、WebhookがAPI呼び出しを18分の1に削減します。難しいのは安全に実装することです。HMAC検証、冪等性、そしてどのベンダーが実際にコールバック機構に対応しているかの把握が必要です。

**1日50件未満のジョブ、またはユーザーが待っている場合はポーリングを使う。1日約100件を超え、ジョブがバックグラウンドで実行されるならWebhookに切り替える。**これが2文にまとめた判断基準です。以下は実装の詳細であり、「正しく機能するWebhook」と「午前2時に黙って文字起こしを取りこぼすWebhook」の分かれ目となる部分です。

リソースの計算

具体例を挙げます。1日200件のジョブを文字起こしするとします。平均処理時間は3分です。

5秒ごとのポーリング:

  • 各ジョブ:平均36回のポーリングリクエスト(3分 × 12回/分)
  • 1日あたり:7,200リクエスト
  • そのうち結果を含むリクエスト:約1%

Webhook:

  • 各ジョブ:送信1回 + Webhook配信1回
  • 1日あたり:送信200回 + Webhook200回 = 400イベント
  • イベントの100%が結果を含む

このボリュームでは、Webhookの方が18倍効率的です。1日2,000件になると、ポーリングはAPIの両側(呼び出し元と提供元)で本当に高コストになり、多くのプロバイダーはジョブ送信への制限より先にステータス確認へのレート制限をかけ始めます。

ポーリングが依然として正しい選択となるケース

**公開HTTPエンドポイントがない。**ローカル開発環境、バックエンドを持たないモバイルファーストのアプリ、VPNの内側にある社内ツールは、Webhookの配信を受け取れません。ポーリングに必要なのは外向きのHTTP通信だけです。

**1日50件未満のボリューム。**リソースコストは無視できる程度です。この規模では、Webhookレシーバーのインフラよりも、ポーリングループのシンプルさが勝ります。

**ユーザーが画面を見ている。**ユーザーがインタビュー音声をアップロードし、進行状況バーをじっと見ています。ポーリングならUIを更新するためのデータが手に入ります。Webhookの場合、完了シグナルをブラウザまで中継するために、追加の仕組み(WebSocket、Server-Sent Events、プッシュサービスなど)が必要になります。動作するポーリングループの実装例は文字起こしAPIを使った開発を参照してください。

Webhookへ切り替えるべきタイミング

3つのサインがあります:

**ボリュームが1日100件のラインを超える。**Webhookレシーバーのインフラコストは、API呼び出しの削減効果によって1週間以内に元が取れます。

**ユーザーが見ていないのでレイテンシは問題にならない。**録音をバックグラウンドで処理し、後でクエリできるようデータベースに格納するケースです。ユーザーは次にダッシュボードを開いたときに結果を見ればいいのです。

**結果を複数宛先に振り分ける必要がある。**Webhookなら、データベースへの書き込み、Slack通知、HubSpotの更新を同時にトリガーできます。ポーリングではアプリケーションコード内で逐次処理するしかありません。振り分けの具体的なレシピは、文字起こしとSlackの連携NotionHubSpotの記事を参照してください。

実際にWebhookに対応している文字起こしAPI

これはドキュメントが示唆する以上にばらついています。

ベンダー非同期方式配信用パラメータ署名検証リトライ
AssemblyAIWebhook(POST)ボディ内のwebhook_urlカスタムヘッダー(名前と値を自分で設定)10回、間隔10秒
Deepgramコールバック(POST)callbackクエリパラメータdg-tokenヘッダー(APIキー識別子)10回、間隔30秒
Rev.aiWebhook(POST)notification_configオブジェクト設定による認証ヘッダー最長24時間、30分ごと
AWS TranscribeポーリングのみGetTranscriptionJobなしなし
Google Cloud STTポーリングのみ(長時間実行オペレーション)Operations.getなしなし
OpenAI Whisper同期のみなしなしなし

AWS TranscribeにはWebhook配信がまったくありません。ジョブを送信したら、TranscriptionJobStatusCOMPLETEDになるまでGetTranscriptionJobをポーリングします。Google Cloud Speech-to-TextのLongRunningRecognizeはオペレーションハンドルを返すので、doneがtrueになるまでOperations.getでポーリングします。OpenAIの/v1/audio/transcriptionsエンドポイントは完全に同期型で、レスポンスが返るかタイムアウトするまでブロックされます。

私の見解:非同期配信に対応している3社の中で、本番環境向きなのはDeepgramのコールバック機構です。AssemblyAIは完了時にtranscript_idstatusフィールドしか配信しないため、実際の文字起こし結果を取得するには必ずもう1回APIを呼ぶ必要があります。Deepgramは完全な結果をコールバックURLに送ってくれます。

主要APIの課金体系を詳しく知りたい方は、2026年版 音声認識APIの料金をご覧ください。

Webhookのセットアップ(ConvertAudioToText API)

登録用のリクエスト:

curl -X POST https://api.convertaudiototext.com/api/v1/dashboard/webhooks \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/transcription",
    "events": ["job.completed", "job.failed"]
  }'

レスポンスにはsecretフィールドが含まれます。必ず保存してください。このシークレットはサーバー側で生成され、作成時に一度だけ返されます。復元可能な形では保存されません。

利用可能なイベント:job.queuedjob.processingjob.completedjob.failed。ほとんどの連携ではjob.completedjob.failedのみを購読します。

ConvertAudioToTextの音声アップロードツール - ここから送信されたジョブは完了時にWebhook配信をトリガーします
ConvertAudioToTextの音声アップロードツール - ここから送信されたジョブは完了時にWebhook配信をトリガーします

HMAC署名の検証

すべての配信にはX-Webhook-Signatureヘッダーが含まれ、値の形式はsha256=<hex>です。署名はHMAC-SHA256(secret, raw_body)で計算されます。

Nodeの場合:

import crypto from 'crypto';
import express from 'express';

const app = express();

app.post('/webhooks/transcription',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.header('X-Webhook-Signature');
    const expected = 'sha256=' + crypto
      .createHmac('sha256', process.env.WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
      return res.status(401).send('Invalid signature');
    }

    const event = JSON.parse(req.body.toString());
    handleTranscript(event);
    res.sendStatus(200);
  }
);

Pythonの場合:

import hmac
import hashlib

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

timingSafeEqual(Node)またはhmac.compare_digest(Python)を使ってください。単純な文字列比較はレスポンスタイミングを通じて情報を漏らします。

**検証後にボディをパースする。決してその前にパースしないこと。**ハンドラーが実行される前にJSONをパースするミドルウェアは生のバイト列を消費してしまうため、署名の計算が不可能になります。上記のexpress.raw()の例は意図的なものです。まず生のバッファを取得し、それからパースします。

冪等性

同じイベントに対して配信ワーカーが複数回発火することがあります。ネットワークは障害を起こします。ハンドラーがイベントを処理した後、200を返す前にクラッシュすることもあるでしょう。ベンダーはリトライし、ハンドラーは再び実行されます。

各イベントには一意のevent_idがあります(ペイロード内で取得可能)。安全なパターンは次のとおりです:

async function handleTranscript(event) {
  const inserted = await db.query(
    `INSERT INTO processed_events (event_id) VALUES ($1)
     ON CONFLICT DO NOTHING RETURNING id`,
    [event.data.job_id + ':' + event.event]
  );

  if (inserted.rowCount === 0) return;

  await saveTranscript(event.data.job_id, event.data.result_url);
}

ON CONFLICT DO NOTHINGにより、重複した配信は静かに終了します。最初の配信が優先され、重複はエラーを出さずに無視されます。

リトライ動作

ConvertAudioToTextの配信ワーカーは、失敗した配信を次のスケジュールでリトライします(合計5回):

試行遅延
1即時
21分
35分
415分
51時間
66時間

5回失敗すると、配信はfailedとしてマークされます。配信履歴の確認と手動リトライはWebhookダッシュボードから行えます。また、リトライ用エンドポイントも使えます:

curl -X POST https://api.convertaudiototext.com/api/v1/dashboard/webhooks/deliveries/{delivery_id}/retry \
  -H "Authorization: Bearer $API_KEY"

**ハンドラーを正しく動作させるために:**成功には2xx、恒久的な失敗には4xx(リトライ不要)、一時的な失敗には5xx(リトライしてほしい)を返します。30秒以内に応答してください。処理に時間がかかる場合は、即座にackを返して実際の作業をキューに入れます:

app.post('/webhooks/transcription', (req, res) => {
  validateSignature(req);
  jobQueue.push(req.body);
  res.sendStatus(200); // ack first, process after
});

ローカル開発

Webhookには公開HTTPS URLが必要です。実際に使える2つの方法:

**ngrokまたはcloudflaredトンネル。**一時的なURLでローカルサーバーをインターネットに公開します。

ngrok http 3000
# Use the resulting https URL as your webhook URL

**デュアルモードのハンドラー。**本番ではWebhook、ローカル開発ではポーリング。環境変数で挙動を切り替えます。本番コードを変更せずに、ローカルのフィードバックループをシンプルに保てます。

本番環境のパターン

**データベース直書き。**ハンドラーが文字起こし結果をPostgresやMongoDBに書き込み、200を返します。下流のコードはデータベースにクエリします。最もシンプルなパターンで、どんな規模でも機能します。

**直接連携。**ハンドラーがSlackに投稿したり、Notionページを作成したり、HubSpotのコンタクトを更新したりします。連携先が主な宛先である場合に適しています。下流がダウンしていれば大きく失敗(5xx)するため、自動的にリトライが走ります。

**イベントバス。**ハンドラーがKafka、SNS、社内pub/subトピックにパブリッシュします。複数のコンシューマーが同じイベントを独立して処理します。Webhookの受信と下流処理を分離できます。複数システムが同じ文字起こし結果を消費する大規模パイプラインに適したパターンです。パイプラインアーキテクチャの詳細は大規模プロジェクト向けの一括文字起こしを参照してください。

両方のパターンを組み合わせる

両方使いたい場面もあります。ユーザーがファイルをアップロードし、バックエンドがジョブを送信してUI更新のためにポーリングを開始します。完了するとWebhookが発火し、データベース内の正式なレコードになります。ユーザーは即座にフィードバックを得られ、バックエンドはUIが開いていることに依存しない確実な非同期シグナルを得られます。

このデュアルアプローチはコード量が増えますが、ユーザーが大切なファイルの結果を能動的に待っている間も、システムがバックグラウンドジョブを確実に処理できるという、最高のユーザー体験を実現します。

バックエンド連携を構築せずに、きれいな文字起こしだけが必要なら、ConvertAudioToTextがブラウザ上でパイプライン全体を処理します。APIキーは不要です。

FAQ

文字起こしでWebhookの代わりにポーリングを使うべきケースは?

公開HTTPエンドポイントがない場合(ローカル開発環境、VPN内限定のツール、バックエンドを持たないモバイルアプリ)、1日50件未満のボリュームの場合、またはユーザーが待っていて進行状況バーをリアルタイムに更新する必要がある場合はポーリングを使います。ボリュームが少ないうちは、Webhookのインフラコストをかけるだけの理由がポーリングにはありません。

主要な文字起こしAPIのうち、どれがWebhookに対応していますか?

AssemblyAIはwebhook_urlパラメータ経由でWebhookに対応しています(リトライ10回、タイムアウト10秒)。Deepgramはcallbackクエリパラメータに対応し、dg-tokenヘッダーでの検証と30秒ごとのリトライ10回をサポートします。Rev.aiはnotification_configオブジェクトを使用し、最大24時間のリトライを行います。AWS TranscribeとGoogle Cloud Speech-to-Textはポーリングが必要です。AWSはGetTranscriptionJob、GoogleはOperations.getの長時間実行オペレーションインターフェースを使用します。OpenAI Whisperは完全に同期型で、非同期配信には一切対応していません。

Webhookが正しい送信元から来たことを検証するには?

標準的なパターンはHMAC-SHA256です。プロバイダーが共有シークレットで生のリクエストボディに署名し、その結果をヘッダーに入れます。ハンドラー側で同じHMACを再計算し、タイミングセーフな等価比較関数で照合します。平文の文字列比較はタイミングを通じて情報を漏らすため、絶対に使わないでください。Deepgramは別の仕組み(APIキーに紐づくdg-tokenヘッダー)を採用しているため、手法はベンダーごとに異なります。

Webhookハンドラーは何を返すべきですか?また、どのくらいの速さで?

受領の確認として2xxステータスコードを返します。返信はベンダーのタイムアウト時間内に行います(30秒が一般的)。実際の処理が重い場合は、即座にackを返して処理をキューに入れます。リトライしてほしい一時的な失敗には5xx、リトライ不要の恒久的な失敗には4xxを返します。応答が遅いとリトライストームが発生し、配信キューが詰まる原因になります。

参考資料

Try transcription free

Convert any audio or video to clean, unwatermarked text — speaker labels, timestamps, and AI summaries included. First 10 minutes free, no account.

Related Articles