Polling vs webhooks
Two ways to find out when a transcription is done. Pick polling to keep things simple, webhooks to keep things fast.
TL;DR
Side-by-side
| Aspect | Polling | Webhooks |
|---|---|---|
| Latency | 3–5s typical (you choose) | Usually <1s after completion |
| Infra required | None — just an HTTP client | Public HTTPS endpoint |
| Reliability | You control retries | Up to 5 retries with backoff |
| Idempotency | Trivial — same GET is safe | Must dedupe on X-QuillAI-Delivery |
| Best for | Prototypes, CLIs, CI | Production apps, real-time UX |
Polling
Create a transcription, then GET /v1/transcriptions/{id} on an interval until status is one of completed, failed, or cancelled. You control the cadence — we don't rate-limit reasonable polling, but be kind.
ID="trs_01HZX9K7Q2M4YV8BTA6JRN3PDE"
DELAY=3
while :; do
RESP=$(curl -s https://api.quillhub.ai/v1/transcriptions/$ID \
-H "Authorization: Bearer $QAI_KEY")
STATUS=$(echo "$RESP" | jq -r .status)
case "$STATUS" in
completed|failed|cancelled) echo "$RESP" | jq .; break ;;
esac
sleep $DELAY
DELAY=$(( DELAY < 10 ? DELAY + 2 : 10 ))
doneTip: use jittered exponential backoff for long jobs (start at 3s, widen to 5s then 10s). Don't hammer the API at 100ms — you'll just burn round-trips.
Webhooks
Pass webhook_url when creating the transcription and we POST the finished Transcription object to that URL as soon as it's ready. See Webhooks for signature verification and retry rules.
curl -X POST https://api.quillhub.ai/v1/transcriptions \
-H "Authorization: Bearer $QAI_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://youtu.be/dQw4w9WgXcQ",
"webhook_url": "https://your-app.example.com/hooks/quillai"
}'When to use which
- You're writing a script, notebook, or CI job.
- You don't have a public HTTPS endpoint to receive callbacks.
- Latency doesn't matter — a few seconds of lag is fine.
- You're running a production app and need low-latency updates.
- You want to free up workers instead of blocking on a poll loop.
- You're processing jobs at scale and polling would multiply your API calls.
Hybrid pattern
In practice, webhook delivery succeeds nearly every time — but networks get weird. After 5 failed retries we stop trying. A small cron that scans for jobs stuck in processing longer than an hour and GETs their current status will catch any orphans without adding real load.
// Runs hourly via cron. Picks up any job whose webhook was abandoned
// after 5 delivery attempts and reconciles it with the API.
const stale = await db.jobs.findMany({
where: { status: "processing", createdAt: { lt: oneHourAgo() } },
});
for (const job of stale) {
const res = await fetch(`https://api.quillhub.ai/v1/transcriptions/${job.id}`, {
headers: { Authorization: `Bearer ${process.env.QAI_KEY}` },
});
const t = await res.json();
if (["completed", "failed", "cancelled"].includes(t.status)) {
await db.jobs.update({ where: { id: job.id }, data: { status: t.status, result: t } });
}
}Pitfalls
- Polling too aggressively — sub-second intervals add no real-world latency win and waste round-trips.
- Not deduping webhook deliveries — always check
X-QuillAI-Deliveryand skip IDs you've already processed. - Exposing a non-HTTPS webhook URL. We refuse to deliver to http:// endpoints.