Should I use webhooks or polling for a transcription API?
Short answer
Webhooks for primary delivery, polling as a backup — the most robust production setup. Polling alone wastes requests; webhooks alone risk lost events.
Transcription jobs are typically async: you upload a file, the API returns a `transcription.id`, and the result arrives later. How do you find out it's done?
Polling Your code asks the API periodically: "Is job X done?" Typical: GET `/v1/transcriptions/{id}/status` every 2-5 seconds. When status flips to `completed`, you fetch the result.
Pros: simple, no public URL needed, works behind firewalls and inside background jobs. Cons: lots of empty requests (a 1h audio file processing in 2 min costs 60-100 polls), rate-limit risk, max latency = polling interval.
Webhooks You register a URL; the API calls it when the job finishes. POST with a body like `{ "event": "transcription.completed", "id": "…", "result": { … } }`. Signed with HMAC-SHA256 so you can verify it's really the provider.
Pros: no empty requests, sub-second latency, scales to millions of jobs. Cons: needs a public URL (or tunnel like ngrok in dev), you must implement retry, idempotency, and signature verification. If the webhook delivery fails (server down, network glitch), the event can be lost.
The right answer: both Production systems combine the two. Webhooks for primary delivery — fast, efficient. Polling as a backstop every 5-10 minutes for jobs that have been queued > 30 min — catches cases where the webhook was lost.
Concrete pattern ``` // On upload const job = await api.transcribe({ file, webhook_url: "https://…" }) await db.insert({ id: job.id, status: "queued" })
// Webhook endpoint app.post("/webhook", (req) => { if (!verifySignature(req)) return 401 await db.update(req.body.id, { status: "completed", result: req.body.result }) })
// Backup job (cron, every 10 min) const stuck = await db.where("status", "queued").and("createdAt", "<", now-30min) for (const j of stuck) { const fresh = await api.getStatus(j.id) if (fresh.status === "completed") await db.update(j.id, fresh) } ```
DeepScript specifics We support both: register webhooks via `POST /v1/webhooks` (events: `transcription.completed`, `transcription.failed`, `balance.low`), poll via `GET /v1/transcriptions/{id}/status`. For real-time UI in the browser, we also expose SSE (`GET /v1/transcriptions/{id}/events`) — server-sent events stream status updates straight to the client without opening a WebSocket channel.
Related questions
Still have a question?
Three transcriptions free to try. Or drop us a line — we answer within 24 hours, compliance questions included.