Webhooks
Receive real-time notifications when your jobs complete or fail.
Setup
- Navigate to Settings
- Enter your webhook URL (must be HTTPS)
- Save your settings
- Copy your webhook secret for signature verification
Webhook Payload
When a job status changes, we'll send a POST request to your webhook URL:
json
{
"event": "job.completed",
"timestamp": "2025-11-23T12:34:56Z",
"data": {
"jobId": "job_abc123",
"status": "merged",
"apiProduct": "dub",
"duration": 125.5,
"cost": 1.20,
"outputs": [
{
"url": "https://s3.amazonaws.com/bucket/path.mp4?X-Amz-Algorithm=...",
"expiresAt": "2025-11-23T13:34:56Z",
"sizeBytes": 45678901
}
]
}
}Payload Fields
| Field | Type | Description |
|---|---|---|
| event | string | Event type: "job.created", "job.processing", "job.completed", or "job.failed" |
| timestamp | string | ISO 8601 timestamp when the event occurred |
| data.jobId | string | Unique job identifier |
| data.status | string | Current job status (see Status Values below) |
| data.apiProduct | string | API product: "transcribe", "translate", "voiceover", or "dub" |
| data.duration | number | Duration in seconds (for audio/video jobs) |
| data.wordCount | number | Word count (for translation jobs) |
| data.cost | number | Job cost in USD |
| data.outputs | array | Presigned download URLs (only for completed jobs, expires in 1 hour) |
Status Values
The status field contains the actual processing status, which varies by API product:
Transcribe workflow:
created→transcribing→transcribed(completed)
Translate workflow:
created→transcribing→transcribed→translating→translated(completed)
Voiceover workflow:
created→dubbing→dubbed→merging→merged(completed)
Dub workflow:
created→transcribing→transcribed→translating→translated→dubbing→dubbed→merging→merged(completed)
Failed states (all workflows):
failed- Processing errorrejected- Invalid input, insufficient balance, or file too large
Event Types
The event field maps statuses to logical events:
- job.created: Job has been created (status:
created) - job.processing: Job is being processed (statuses:
transcribing,transcribed,translating,translated,dubbing,dubbed,merging) - job.completed: Job finished successfully (status:
transcribedfor transcribe,translatedfor translate,mergedfor voiceover/dub) - job.failed: Job failed or was rejected (statuses:
failed,rejected)
Signature Verification
All webhook requests include a signature in the X-Redner-Signature header. Verify it to ensure the request came from Redner:
Python
python
import hmac
import hashlib
def verify_webhook_signature(payload, signature, secret):
expected_signature = 'sha256=' + hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
# Usage
payload = request.body.decode('utf-8')
signature = request.headers.get('X-Redner-Signature')
secret = 'your_webhook_secret'
if verify_webhook_signature(payload, signature, secret):
# Process webhook
data = json.loads(payload)
else:
# Invalid signature
return 401JavaScript
javascript
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Usage
const payload = JSON.stringify(req.body);
const signature = req.headers['x-redner-signature'];
const secret = 'your_webhook_secret';
if (verifyWebhookSignature(payload, signature, secret)) {
// Process webhook
} else {
// Invalid signature
res.status(401).send('Invalid signature');
}Java
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
public boolean verifyWebhookSignature(String payload, String signature, String secret) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
mac.init(secretKey);
byte[] hash = mac.doFinal(payload.getBytes());
String expectedSignature = "sha256=" + bytesToHex(hash);
return MessageDigest.isEqual(
signature.getBytes(),
expectedSignature.getBytes()
);
} catch (Exception e) {
return false;
}
}
private String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}Best Practices
- Always verify the webhook signature
- Respond with 200 OK quickly (process asynchronously if needed)
- Implement retry logic for failed webhook deliveries
- Use HTTPS for your webhook endpoint
- Log all webhook events for debugging