Documentation

Webhooks

Receive real-time notifications when your jobs complete or fail.

Setup

  1. Navigate to Settings
  2. Enter your webhook URL (must be HTTPS)
  3. Save your settings
  4. 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

FieldTypeDescription
eventstringEvent type: "job.created", "job.processing", "job.completed", or "job.failed"
timestampstringISO 8601 timestamp when the event occurred
data.jobIdstringUnique job identifier
data.statusstringCurrent job status (see Status Values below)
data.apiProductstringAPI product: "transcribe", "translate", "voiceover", or "dub"
data.durationnumberDuration in seconds (for audio/video jobs)
data.wordCountnumberWord count (for translation jobs)
data.costnumberJob cost in USD
data.outputsarrayPresigned 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:

  • createdtranscribingtranscribed (completed)

Translate workflow:

  • createdtranscribingtranscribedtranslatingtranslated (completed)

Voiceover workflow:

  • createddubbingdubbedmergingmerged (completed)

Dub workflow:

  • createdtranscribingtranscribedtranslatingtranslateddubbingdubbedmergingmerged (completed)

Failed states (all workflows):

  • failed - Processing error
  • rejected - 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: transcribed for transcribe, translated for translate, merged for 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 401

JavaScript

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