Guide to Rev.ai Webhooks: Features and Best Practices
Rev.ai webhooks tell you when an asynchronous transcription job is done. You submit an audio or video file, Rev.ai transcribes it in the background, and when the job finishes or fails it calls your URL so you can fetch the transcript and move on: summarise it, index it, or show it to a user. Rev AI, from Rev, calls these callbacks webhooks.
This guide covers how Rev.ai's per-job webhooks are configured, what the callback contains, how to authenticate it with the Authorization header you set on the job, the retry behaviour, and the best practices for production.
What are Rev.ai webhooks?
Rev.ai has no webhook settings page. Instead, each asynchronous job can carry a notification_config object with a callback url and, optionally, auth_headers. When the job either completes or fails, Rev.ai sends a POST to that URL with the job's details in the body.
There are no event types. Every callback is about one job, and job.status tells you whether it finished (transcribed) or not (failed). job.id ties the callback back to the job you submitted.
Rev.ai doesn't sign callbacks. Its authentication option is auth_headers, which has two constraints: Authorization is the only header allowed, and its value must take the form <scheme> <token>, for example Bearer <token>. Rev.ai sends that header with the callback, and your receiver compares it.
An older callback_url parameter sends the same callback without any auth headers. It's deprecated, can't be combined with notification_config, and should be avoided.
Rev.ai webhook features
| Feature | Details |
|---|---|
| Configuration | Per job: notification_config on POST /speechtotext/v1/jobs |
| Trigger | The job completes or fails |
| Method | POST with a JSON body |
| Payload | { "job": { ... } }: a job summary |
| Event types | None. job.status is transcribed or failed |
| Authentication | auth_headers with Authorization: <scheme> <token> only |
| Signature | None |
| Success | A 200 response |
| Retries | Every 30 minutes until a 200 or 24 hours have passed |
| Deprecated option | callback_url, with no auth headers |
| Source IPs | Not published |
The callback payload
A successful job produces a callback like this:
{
"job": {
"id": "Umx5c6F7pH7r",
"status": "transcribed",
"created_on": "2018-05-05T23:23:22.29Z",
"duration_seconds": 356.24
}
}
Rev.ai's own example of this body has a trailing comma after duration_seconds, so don't copy it into a test fixture as-is. A failed job looks like this:
{
"job": {
"id": "Umx5c6F7pH7r",
"status": "failed",
"created_on": "2018-05-05T23:23:22.29Z",
"failure": "download_failure",
"failure_detail": "Failed to download media file. Please check your url and file type"
}
}
The callback doesn't include the transcript. On transcribed, fetch it with GET https://api.rev.ai/speechtotext/v1/jobs/{id}/transcript using your access token. On failed, failure gives a machine-readable reason and failure_detail a human-readable one.
See Rev.ai webhook payloads in action. Inspect and replay sample Rev.ai webhook payloads in Hookdeck Inspect, with no account or setup required.
Setting up Rev.ai webhooks
Choose the Authorization value your receiver will accept. It needs a scheme, so generate something like Bearer followed by a long random token, and store the whole string in your app's environment:
REV_AI_AUTHORIZATION="Bearer $(openssl rand -hex 32)"
Then pass it in notification_config on every job you submit:
curl -X POST https://api.rev.ai/speechtotext/v1/jobs \
-H "Authorization: Bearer $REV_AI_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"source_config\": { \"url\": \"https://www.rev.ai/FTC_Sample_1.mp3\" },
\"notification_config\": {
\"url\": \"https://example.com/webhooks/rev-ai\",
\"auth_headers\": { \"Authorization\": \"$REV_AI_AUTHORIZATION\" }
},
\"metadata\": \"order-1234\"
}"
Don't confuse the two Authorization values. The one on the request authenticates you to Rev.ai with your access token. The one inside auth_headers is the credential Rev.ai sends to your callback URL, and should never be your Rev.ai access token. notification_config isn't echoed back in the submission response, so keep track of the URL and credential in your own code.
For local development, use the Hookdeck CLI: hookdeck listen 3000 rev-ai --path /webhooks/rev-ai gives you a public HTTPS URL that forwards to your local server, plus a web UI for inspecting and replaying deliveries, with no account required. Use the generated URL as notification_config.url.
With Hookdeck. Create a Rev.ai source in Hookdeck and paste the full Authorization value from auth_headers into Authorization Header, including the Bearer prefix. Hookdeck compares the whole header, so abc123 won't match an incoming Bearer abc123. Use the source URL as notification_config.url.
Securing Rev.ai webhooks
Compare the incoming Authorization header with the full value you set, scheme included, in constant time. Check lengths first, because crypto.timingSafeEqual throws on buffers of different lengths. Return 200 only when you've accepted the callback, since that's what stops Rev.ai's retries.
const crypto = require('crypto');
const express = require('express');
const app = express();
function safeEqual(a, b) {
const x = Buffer.from(String(a), 'utf8');
const y = Buffer.from(String(b), 'utf8');
return x.length === y.length && crypto.timingSafeEqual(x, y);
}
function verifyRevAiRequest(authorization) {
const expected = process.env.REV_AI_AUTHORIZATION; // e.g. "Bearer 3f9c..."
if (!expected || typeof authorization !== 'string') return false;
return safeEqual(authorization, expected); // whole value, scheme included
}
app.post('/webhooks/rev-ai', express.raw({ type: '*/*' }), (req, res) => {
if (!verifyRevAiRequest(req.headers.authorization)) {
return res.sendStatus(401);
}
let body;
try {
body = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
const job = body.job || {};
res.sendStatus(200); // a 200 stops the retries
if (job.status === 'transcribed') {
// queue a job to GET /jobs/{job.id}/transcript
} else if (job.status === 'failed') {
// record job.failure and job.failure_detail
}
});
The same check in Python:
import hmac
import os
def verify_rev_ai_request(authorization) -> bool:
expected = os.environ.get("REV_AI_AUTHORIZATION")
if not expected or not authorization:
return False
return hmac.compare_digest(authorization.encode("utf-8"), expected.encode("utf-8"))
A static Authorization value authenticates the sender, but it doesn't protect the body, and a captured request can be replayed because nothing is signed or timestamped. Use an HTTPS callback URL and keep the value out of logs. Because the credential is set per job, rotation means submitting new jobs with a new value while accepting the old one until jobs already in flight have called back.
Make Rev.ai webhooks production-ready. Hookdeck Event Gateway checks the
Authorizationheader on every callback, stores each one durably, and keeps retrying delivery to your service well past Rev.ai's 24-hour window.
Rev.ai webhook limitations and pain points
A 24-hour retry window
The Problem: If your receiver is down or returning errors for more than a day, the callback is lost, and the only way to find out a job finished is to poll for it.
Why It Happens: Rev.ai retries every 30 minutes until it gets a 200 or 24 hours have passed. Anything other than a 200 counts as a failure.
Workarounds:
- Return
200as soon as the callback is authenticated and stored. - Run a periodic job that lists recent jobs and fetches any you haven't seen a callback for.
How Hookdeck Can Help: Hookdeck answers Rev.ai with a 200 as soon as the callback is stored, then delivers to your service with automatic retries on a schedule you control. Issues alert you when deliveries fail, and you can replay any stored callback without resubmitting the job.
One header, one format
The Problem: Authentication fails even though the token is right.
Why It Happens: Rev.ai only allows the Authorization header in auth_headers, and its value must include a scheme. A receiver that stores only the token, or strips the Bearer prefix from one side and not the other, never matches.
Workarounds:
- Store the whole value, scheme included, and compare it as-is.
- Keep the scheme and token in one environment variable so the two can't drift.
How Hookdeck Can Help: Hookdeck's Rev.ai source compares the whole Authorization header with the value you paste. A request that doesn't match still gets a 200 response, but Hookdeck marks it as failed verification and creates no event, so it never reaches your destination. The stored request shows the exact header that arrived, which makes a prefix mismatch easy to spot.
No signature, and a deprecated unauthenticated option
The Problem: Anyone who knows the callback URL and credential can post a fake result, and jobs submitted with callback_url carry no credential at all.
Why It Happens: Rev.ai's only callback authentication is the static Authorization header, and the older callback_url predates it.
Workarounds:
- Always use
notification_configwithauth_headers. - Before acting on a callback, fetch the job by
job.idand confirm its status through the API.
How Hookdeck Can Help: Every callback is stored with its headers and body, so you can audit which jobs called back, and with what, before your handler acts.
Best practices
Use the callback as a trigger, not the data
The body is a job summary. Fetch the transcript, or other job details, from the API using job.id.
Tag jobs with your own identifiers
Pass metadata when you submit the job, and store the returned job ID against your own record, so a callback can be matched to the right upload or user.
Handle both outcomes
Route on job.status. Treat failed as a real event: record failure and failure_detail, and decide whether to resubmit with a corrected media URL.
Make handlers idempotent
Retries can deliver the same callback more than once. Record job.id with its status and skip repeats. See our guide to webhook idempotency.
Acknowledge fast
Return 200 before downloading transcripts or calling an LLM, and do that work in the background. See why to process webhooks asynchronously, and for AI pipelines, Hookdeck for AI agents and LLMs.
Conclusion
Rev.ai sends one unsigned POST per asynchronous job when it completes or fails, configured per job through notification_config. Authenticate it by comparing the whole Authorization value, scheme included, in constant time, route on job.status, fetch the transcript by job.id, and return 200 quickly because anything else is retried for only 24 hours.
Hookdeck Event Gateway checks the Authorization header, stores every callback and retries delivery to your service, so a finished transcript is never lost to an outage on your side.
Get started with Hookdeck for free and handle Rev.ai webhooks reliably in minutes.