Webhooks vs Polling for Transcripts: The Async Choice
apiwebhooksdevelopers

Webhooks vs Polling for Transcripts: The Async Choice

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

Summarize this article with:

TL;DR

Poll when you have under 50 jobs per day or a user is actively watching a progress bar. Switch to webhooks when volume crosses roughly 100 jobs per day, when jobs run in the background, or when results need to fan out to multiple destinations. The math is clear: at 200 jobs per day, webhooks cut API calls by 18x. The tricky part is doing them safely: HMAC verification, idempotency, and knowing which vendors actually support a callback mechanism.

Use polling when you have fewer than 50 jobs a day or a user is actively waiting. Switch to webhooks above roughly 100 jobs a day when jobs run in the background. That is the decision in two sentences. Everything below is the implementation detail that makes the difference between a webhook that works and one that quietly loses transcripts at 2 AM.

The Resource Math

A concrete example. You transcribe 200 jobs per day. Each takes 3 minutes on average.

Polling every 5 seconds:

  • Each job: 36 poll requests on average (3 min x 12 polls/min)
  • Daily: 7,200 poll requests
  • Useful results in those requests: roughly 1%

Webhooks:

  • Each job: 1 submit + 1 webhook delivery
  • Daily: 200 submits + 200 webhooks = 400 events
  • 100% of events carry a result

Webhooks are 18x more efficient at this volume. At 2,000 jobs per day, polling becomes genuinely expensive on both sides of the API, and most providers start rate-limiting you on status checks before they rate-limit you on submissions.

When Polling Is Still the Right Call

You have no public HTTP endpoint. Local development environments, mobile-first apps without a backend, and internal tools behind a VPN cannot receive webhook deliveries. Polling requires only outbound HTTP.

Volume is under 50 jobs per day. The resource cost is negligible. The simplicity of a polling loop outweighs the infrastructure of a webhook receiver.

The user is actively watching. A user uploaded an interview and is staring at a progress bar. Polling gives you something to update the UI with. Webhooks would require additional plumbing (WebSocket, server-sent events, or a push service) to relay the completion signal back to the browser. See building with transcription APIs for a working polling loop.

When to Switch to Webhooks

Three signals:

Volume passes the 100-job-per-day mark. The infrastructure cost of a webhook receiver pays for itself in API call savings within a week.

Latency does not matter because the user is not watching. Background processing of recordings that land in a database for later querying. The user will see the result whenever they next open the dashboard.

Results need to fan out. A webhook can trigger your database write, a Slack notification, and a HubSpot update simultaneously. Polling has to do this serially in your application code. See integrating transcription with Slack, Notion, or HubSpot for the fan-out recipes.

Which Transcription APIs Actually Support Webhooks

This varies more than the documentation implies.

VendorAsync mechanismDelivery paramSignature verificationRetries
AssemblyAIWebhook (POST)webhook_url in bodyCustom header (you configure name + value)10 attempts, 10s between each
DeepgramCallback (POST)callback query paramdg-token header (API key identifier)10 attempts, 30s between each
Rev.aiWebhook (POST)notification_config objectAuth header via configEvery 30 min for up to 24 hr
AWS TranscribePoll onlyGetTranscriptionJobN/AN/A
Google Cloud STTPoll only (long-running operation)Operations.getN/AN/A
OpenAI WhisperSynchronous onlyN/AN/AN/A

AWS Transcribe has no webhook delivery at all. You submit a job, then poll GetTranscriptionJob until TranscriptionJobStatus equals COMPLETED. Google Cloud Speech-to-Text's LongRunningRecognize returns an operation handle that you poll with Operations.get until done is true. OpenAI's /v1/audio/transcriptions endpoint is fully synchronous: you block until the response returns or you time out.

My take: Deepgram's callback mechanism is the most production-friendly of the three that support async delivery. AssemblyAI's payload delivers only a transcript_id and a status field on completion, meaning you always need a second API call to fetch the actual transcript. Deepgram sends the full result to your callback URL.

For a deeper look at how the major APIs price their usage, see speech-to-text API pricing 2026.

Setting Up a Webhook (ConvertAudioToText API)

The registration call:

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"]
  }'

The response includes a secret field. Save it. The API generates the secret server-side and returns it once at creation time. It is not stored in a recoverable form.

Available events: job.queued, job.processing, job.completed, job.failed. Most integrations subscribe to job.completed and job.failed only.

ConvertAudioToText audio upload tool - jobs submitted here trigger webhook deliveries on completion
ConvertAudioToText audio upload tool - jobs submitted here trigger webhook deliveries on completion

HMAC Signature Validation

Every delivery includes an X-Webhook-Signature header with the value format sha256=<hex>. The signature is HMAC-SHA256(secret, raw_body).

In 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);
  }
);

In 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)

Use timingSafeEqual (Node) or hmac.compare_digest (Python). Naive string comparison leaks information through response timing.

Parse the body after validating, never before. Middleware that parses JSON before your handler runs will consume the raw bytes, making signature computation impossible. The express.raw() example above is deliberate: you get the raw buffer first, then parse.

Idempotency

The delivery worker may fire more than once for the same event. Networks fail. Your handler may process an event and crash before returning 200. The vendor retries. Your handler runs again.

Each event has a unique event_id (available in the payload). The safe pattern:

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 means duplicate deliveries silently exit. The first delivery wins; duplicates are ignored without throwing errors.

Retry Behavior

The ConvertAudioToText delivery worker retries failed deliveries on this schedule (5 attempts total):

AttemptDelay
1Immediate
21 minute
35 minutes
415 minutes
51 hour
66 hours

After 5 failed attempts, the delivery is marked failed. You can view delivery history and trigger manual retries from the webhook dashboard, or use the retry endpoint:

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

For your handler to behave correctly: return 2xx for success, 4xx for permanent failures (no retry needed), 5xx for transient failures (retry wanted). Respond within 30 seconds. If your processing is slow, ack immediately and queue the real work:

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

Local Development

Webhooks require a public HTTPS URL. Two approaches that work:

ngrok or cloudflared tunnel. Exposes your local server to the internet with a temporary URL.

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

Dual-mode handler. Webhooks in production, polling in local dev. An environment variable switches the behavior. This keeps the local feedback loop simple without changing production code.

Production Patterns

Direct-to-database. The handler writes the transcript to Postgres or MongoDB and returns 200. Downstream code queries the database. Simplest pattern, works at any scale.

Direct integration. The handler posts to Slack, creates a Notion page, or updates a HubSpot contact. Good when the integration is the primary destination. Fails loudly (5xx) if the downstream is down, which triggers retries automatically.

Event-bus. The handler publishes to Kafka, SNS, or an internal pub/sub topic. Multiple consumers process the same event independently. Decouples webhook reception from downstream processing. The right pattern for large pipelines where multiple systems consume the same transcript. See batch transcription for large projects for the pipeline architecture.

Mixing Both Patterns

Sometimes you want both. The user uploads a file. Your backend submits the job and starts polling for UI updates. The webhook fires when done and becomes the canonical record in your database. The user gets immediate feedback; the backend gets a reliable async signal that does not depend on the UI staying open.

This dual approach requires more code but delivers the best user experience when users actively wait for results on files they care about while the system also handles background jobs reliably.

If you just need clean transcripts without building a backend integration, ConvertAudioToText handles the whole pipeline in the browser with no API key required.

FAQ

When should I use polling instead of webhooks for transcription?

Poll when you have no public HTTP endpoint (local dev, VPN-only tools, mobile apps without a backend), when volume is under 50 jobs per day, or when a user is actively waiting and you need to update a progress bar in real time. Webhooks add infrastructure cost that polling does not justify at low volume.

Which major transcription APIs support webhooks?

AssemblyAI supports webhooks via a webhook_url parameter (10 retry attempts, 10-second timeout). Deepgram supports a callback query parameter with dg-token header verification and 10 retries every 30 seconds. Rev.ai uses a notification_config object with up to 24 hours of retries. AWS Transcribe and Google Cloud Speech-to-Text require polling: AWS via GetTranscriptionJob and Google via the Operations.get long-running operation interface. OpenAI Whisper is fully synchronous with no async delivery at all.

How do I validate that a webhook came from the right source?

The standard pattern is HMAC-SHA256: the provider signs the raw request body with your shared secret and puts the result in a header. Your handler recomputes the same HMAC and compares using a timing-safe equal function. Never use a plain string comparison, which leaks information through timing. Deepgram uses a different mechanism (a dg-token header tied to your API key), so the approach varies by vendor.

What should my webhook handler return and how fast?

Return a 2xx status code to acknowledge receipt, and do it within your vendor's timeout window (30 seconds is common). If your actual processing is expensive, ack immediately and queue the work. Return 5xx for transient failures you want retried, 4xx for permanent failures you do not. Slow responses trigger retry storms that can back up your delivery queue.

Sources

Try transcription free

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

Related Articles