Using Speech Revolutions with Django
Integrate Speech Revolutions into a Django app: a view that submits a file for transcription, a model that stores each job's status and progress, and a webhook view that verifies the signature and records the result. The API key stays in Django settings / the server environment — it never reaches the browser.
Keep the key server-side
SpeechRevolutions() reads SPEECHREVOLUTIONS_API_KEY from the environment. Views run on the server, so the client and key never ship to templates or JavaScript.
Install
pip install django speechrevolutions
export SPEECHREVOLUTIONS_API_KEY=stt_...A model to store status + progress
Persist the Speech Revolutions job_id plus a status and a 0–100 progress number. Store the download URL and transcript once the job completes.
from django.db import models
class TranscriptionJob(models.Model):
class Status(models.TextChoices):
PROCESSING = "processing"
COMPLETED = "completed"
FAILED = "failed"
job_id = models.CharField(max_length=64, unique=True, db_index=True)
status = models.CharField(
max_length=16, choices=Status.choices, default=Status.PROCESSING
)
percent = models.FloatField(default=0.0) # overall 0-100
download_url = models.URLField(blank=True, default="")
text = models.TextField(blank=True, default="")
reason = models.CharField(max_length=255, blank=True, default="")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)A view that submits
Take the uploaded file, call submit() to get a job id without blocking the request, and create the row. Pass a callback_url so Speech Revolutions notifies you when the job finishes — the webhook view below fills in the result.
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.views.decorators.http import require_POST
from speechrevolutions import SpeechRevolutions
from .models import TranscriptionJob
client = SpeechRevolutions() # reads SPEECHREVOLUTIONS_API_KEY
@require_POST
def start_transcription(request):
upload = request.FILES["file"]
# submit() returns a job id without holding the request open.
job_id = client.submit(
upload.read(),
speaker_labels=True,
callback_url=request.build_absolute_uri("/webhooks/stt/"),
)
TranscriptionJob.objects.create(job_id=job_id)
return JsonResponse({"job_id": job_id, "status": "processing"})
def job_progress(request, job_id):
# get() would raise DoesNotExist and return a 500 for an id that has been
# mistyped, expired, or never existed. 404 is the honest answer.
job = get_object_or_404(TranscriptionJob, job_id=job_id)
return JsonResponse(
{"status": job.status, "percent": job.percent, "text": job.text}
)Driving the percent field
submit() returns immediately and doesn't stream progress. To move percent between submit and completion, run the blocking transcribe() with on_progress/on_upload_progress callbacks in a background worker (Celery, RQ, or a thread) that writes each update to the row. See Live progress for web apps for the callback-to-bar weighting.
Webhook view with signature verification
Speech Revolutions POSTs a signed JSON body to your callback_url on completion or permanent failure. The signature is HMAC-SHA256 over the raw body in the X-SR-Signature: sha256=<hex> header. Verify against request.body (the exact bytes) and exempt the view from CSRF — it's a server-to-server POST, not a browser form.
import hashlib
import hmac
import json
from django.conf import settings
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from .models import TranscriptionJob
def verify_signature(raw_body: bytes, signature_header: str) -> bool:
expected = "sha256=" + hmac.new(
settings.STT_WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header or "")
@csrf_exempt
@require_POST
def stt_webhook(request):
raw = request.body # verify against the exact bytes received
if not verify_signature(raw, request.headers.get("X-SR-Signature", "")):
return HttpResponse(status=401)
event = json.loads(raw)
# {job_id, status: "completed" | "failed", download_url?, step?, reason?}
try:
job = TranscriptionJob.objects.get(job_id=event["job_id"])
except TranscriptionJob.DoesNotExist:
return HttpResponse(status=404)
if event["status"] == "completed":
job.status = TranscriptionJob.Status.COMPLETED
job.percent = 100.0
job.download_url = event.get("download_url", "")
# Fetch + parse the transcript, then store the text:
# from speechrevolutions import SpeechRevolutions
# job.text = SpeechRevolutions().get_transcript(job.job_id).text
else:
job.status = TranscriptionJob.Status.FAILED
job.reason = event.get("reason", "")
job.save()
return JsonResponse({"ok": True}) # a 2xx acks delivery; 5xx is retriedURLs
from django.urls import path
from transcripts import views
from transcripts.webhooks import stt_webhook
urlpatterns = [
path("transcribe/", views.start_transcription),
path("jobs/<str:job_id>/progress/", views.job_progress),
path("webhooks/stt/", stt_webhook),
]Under the hood
submit() creates the job via the upload flow and returns its id; status and the transcript are fetched later through the jobs endpoints. See the Python SDK for the full surface.