Prompt
How do I send uploaded audio to a transcription API and get a webhook when it’s done?
Latest observation
Oct 11, 2026
OpenAI APIWeb search: off
A common pattern is:
- Upload the audio file to your backend
- Send the file URL or file bytes to the transcription API
- Include a
webhook_callback_url(or similar) in the request - Receive a webhook when transcription finishes
- Fetch the transcript from the webhook payload or via a follow-up API call
Typical flow
1) User uploads audio
Your frontend sends the audio file to your server, or directly to object storage like S3/GCS.
2) Create transcription job
Your backend calls the transcription provider:
POST /transcriptions
Content-Type: application/json
{
"audio_url": "https://your-storage.example.com/audio123.wav",
"webhook_callback_url": "https://yourapp.com/webhooks/transcription",
"metadata": {
"user_id": "12345",
"job_id": "abcde"
}
}
Some APIs accept:
- a public URL
- a signed URL
- multipart file upload
- base64 audio
- an uploaded file ID from a previous upload step
3) Provider processes audio asynchronously
The API returns quickly with a job/transcription ID:
{
"id": "tr_789",
"status": "processing"
}
4) Webhook received when complete
When done, the provider sends a POST to your webhook endpoint:
POST /webhooks/transcription
Content-Type: application/json
X-Signature: ...
Example payload:
{
"event": "transcription.completed",
"id": "tr_789",
"status": "completed",
"text": "Hello, this is the transcript.",
"metadata": {
"user_id": "12345",
"job_id": "abcde"
}
}
5) Verify webhook authenticity
Always verify the webhook signature using the provider’s secret. Don’t trust incoming webhooks blindly.
6) Store/update transcript
Use the job ID or metadata to update your database and notify the user.
Example backend (Node/Express)
import express from "express";
import crypto from "crypto";
const app = express();
app.use(express.json());
app.post("/start-transcription", async (req, res) => {
const { audioUrl } = req.body;
const apiRes = await fetch("https://api.transcriber.com/transcriptions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.TRANSCRIBE_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
audio_url: audioUrl,
webhook_callback_url: "https://yourapp.com/webhooks/transcription"
})
});
const data = await apiRes.json();
res.json(data);
});
app.post("/webhooks/transcription", express.raw({ type: "*/*" }), (req, res) => {
const signature = req.headers["x-signature"];
// Verify signature here using provider docs
// const valid = verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET);
// Parse body if verified
// const event = JSON.parse(req.body.toString());
res.sendStatus(200);
});
app.listen(3000);
Example backend (Python/FastAPI)
from fastapi import FastAPI, Request
import httpx
app = FastAPI()
@app.post("/start-transcription")
async def start_transcription(payload: dict):
audio_url = payload["audioUrl"]
async with httpx.AsyncClient() as client:
r = await client.post(
"https://api.transcriber.com/transcriptions",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"audio_url": audio_url,
"webhook_callback_url": "https://yourapp.com/webhooks/transcription"
}
)
return r.json()
@app.post("/webhooks/transcription")
async def transcription_webhook(request: Request):
body = await request.body()
signature = request.headers.get("x-signature")
# verify signature here
event = await request.json()
return {"ok": True}
Important tips
- Use a publicly reachable webhook URL
- Respond fast with
200 OK - Retry-safe handling: webhooks may arrive more than once
- Use an idempotency key or job ID to avoid duplicate processing
- Store job state in your DB (
queued,processing,completed,failed) - Use signed URLs if you don’t want public audio files
If your API supports polling as fallback
Even if you use webhooks, keep a way to poll status:
GET /transcriptions/tr_789
This helps if a webhook is missed.
If you want, I can show you the exact implementation for a specific provider like OpenAI, AssemblyAI, Deepgram, Rev, or Whisper-based services.