Webhooks provide instant notifications when your video generation tasks complete, fail, or timeout. No more polling!
Setup Guide
Create Webhook Endpoint
Set up an HTTPS endpoint that can receive POST requests. from flask import Flask, request, jsonify
app = Flask( __name__ )
@app.route ( '/webhook/o3-video' , methods = [ 'POST' ])
def handle_webhook ():
data = request.json
if data[ 'status' ] == 'completed' :
print ( f "Video ready: { data[ 'video_url' ] } " )
# Process the completed video
else :
print ( f "Generation failed: { data[ 'message' ] } " )
# Handle the error
return jsonify({ 'status' : 'received' }), 200
Add Webhook URL
Include your webhook URL when creating a generation task. curl -X POST https://platform.runblob.io/v1/kling/o3-video/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Dragon flying over mountains",
"duration": "10",
"mode": "pro",
"callback_url": "https://your-app.com/webhook/o3-video"
}'
Handle Notifications
Your endpoint will receive instant notifications when tasks complete.
Payload Structure
Sent when video generation completes successfully. {
"generation_id" : "550e8400-e29b-41d4-a716-446655440000" ,
"status" : "completed" ,
"video_url" : "https://cdn.example.com/videos/550e8400.mp4" ,
"model" : "kling-o3-video-10s-pro" ,
"message" : null
}
Always "completed" for successful generations
UUID of the completed task
Direct download URL for the generated video
Model configuration used (e.g., kling-o3-video-10s-pro)
Always null for successful generations
Sent when video generation fails with specific error codes. {
"generation_id" : "550e8400-e29b-41d4-a716-446655440000" ,
"status" : "failed" ,
"video_url" : null ,
"model" : "kling-o3-video-10s-pro" ,
"message" : "CONTENT_POLICY_VIOLATION"
}
Always "failed" for failed generations
Always null for failed tasks
Sent when generation exceeds the 15-minute limit. {
"generation_id" : "550e8400-e29b-41d4-a716-446655440000" ,
"status" : "failed" ,
"video_url" : null ,
"model" : "kling-o3-video-10s-pro" ,
"message" : "TIMEOUT"
}
Credits are automatically refunded for timed-out tasks.
Error Codes
Common error codes sent via webhooks:
Code Description Action CONTENT_POLICY_VIOLATIONContent violates usage policies Modify prompt IMAGE_INACCESSIBLEImage URL returned 404/403 Check image URLs INVALID_IMAGEImage format invalid or corrupted Use valid JPEG/PNG TIMEOUTGeneration exceeded 15 minutes Credits refunded TASK_FAILEDGeneral system error Credits refunded, retry MAINTENANCEService temporarily unavailable Retry later
Reliability Features
Retry Policy: Up to 5 attempts with 30-second intervalsBackoff Strategy: Linear backoff between retriesFailure Handling: After 5 failed attempts, the webhook is marked as failed
Request Timeout: 30 seconds per webhook requestConnection Timeout: 10 seconds to establish connectionBest Practice: Respond quickly with a 200 status code, process asynchronously
HTTPS Required: All webhook URLs must use HTTPSIP Allowlist: Consider restricting access to RunBlob’s IP rangesValidation: Always validate the generation_id in your webhook handler
Example Implementations
Flask
Express.js
TypeScript + Next.js
from flask import Flask, request, jsonify
import logging
app = Flask( __name__ )
logging.basicConfig( level = logging. INFO )
@app.route ( '/webhook/o3-video' , methods = [ 'POST' ])
def o3_video_webhook ():
try :
data = request.json
generation_id = data.get( 'generation_id' )
if data.get( 'status' ) == 'completed' :
video_url = data.get( 'video_url' )
model = data.get( 'model' )
logging.info( f "Video ready for { generation_id } : { video_url } ( { model } )" )
# Process successful generation
process_video(generation_id, video_url, model)
else :
error_code = data.get( 'message' )
logging.error( f "Generation failed for { generation_id } : { error_code } " )
# Handle failure
handle_failure(generation_id, error_code)
return jsonify({ 'status' : 'received' }), 200
except Exception as e:
logging.error( f "Webhook error: { str (e) } " )
return jsonify({ 'error' : 'Internal error' }), 500
def process_video ( generation_id , video_url , model ):
"""Process completed video - download, store, notify user"""
# Download video to your storage
# Update database
# Notify user via email/push notification
pass
def handle_failure ( generation_id , error_code ):
"""Handle failed generation - log, refund, notify"""
# Log error for analytics
# Update database with failure reason
# Notify user of failure
pass
if __name__ == '__main__' :
app.run( port = 3000 )
Best Practices
Always return 200 OK immediately. Process the webhook payload asynchronously: app . post ( '/webhook' , async ( req , res ) => {
// Acknowledge receipt immediately
res . status ( 200 ). json ({ status: 'received' });
// Process asynchronously
processWebhookAsync ( req . body ). catch ( console . error );
});
Keep track of generation IDs in your database to prevent duplicate processing: def handle_webhook ( data ):
generation_id = data[ 'generation_id' ]
# Check if already processed
if db.is_processed(generation_id):
return # Skip duplicate
# Mark as processed
db.mark_processed(generation_id)
# Process the video
process_video(data)
Always validate webhook payloads before processing: function validateWebhook ( payload ) {
if ( ! payload . generation_id || ! payload . status ) {
throw new Error ( 'Invalid webhook payload' );
}
if ( payload . status === 'completed' && ! payload . video_url ) {
throw new Error ( 'Missing video_url for completed generation' );
}
return true ;
}
Important: Always return a 200 status code quickly to acknowledge receipt. Perform heavy processing asynchronously to avoid timeouts.