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' , methods = [ 'POST' ])
def handle_webhook ():
data = request.json
if data[ 'status' ] == 'completed' :
print ( f "Video ready: { data[ 'video_url' ] } " )
else :
error_msg = data.get( 'error' , {}).get( 'message' , 'Unknown error' )
print ( f "Generation failed: { error_msg } " )
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/veo/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A sunset over mountains",
"callback_url": "https://your-app.com/webhook"
}'
Handle Notifications
Your endpoint will receive instant notifications when tasks complete.
Payload Structure
Sent when video generation completes successfully. {
"generation_id" : "ffd473d5-5bef-4e14-bf22-8d559be3c19f" ,
"status" : "completed" ,
"prompt" : "A cat playing with a ball in a sunny room" ,
"video_url" : "https://cdn.runblob.io/videos/your-video.mp4" ,
"created_at" : "2025-12-06T21:43:03.374000" ,
"completed_at" : "2025-12-06T21:44:06.143000" ,
"seed" : 319790131 ,
"error" : null
}
UUID of the completed task
Always "completed" for successful generations
Original prompt used for generation
Direct download URL for the generated video
ISO 8601 timestamp when task was created
ISO 8601 timestamp when task completed
Random seed used for generation
Always null for successful generations
Sent when video generation fails with specific error codes. {
"generation_id" : "ffd473d5-5bef-4e14-bf22-8d559be3c19f" ,
"status" : "failed" ,
"prompt" : "A cat playing with a ball in a sunny room" ,
"video_url" : null ,
"created_at" : "2025-12-06T21:43:03.374000" ,
"completed_at" : "2025-12-06T21:44:06.143000" ,
"seed" : 319790131 ,
"error" : {
"message" : "UNSAFE_CONTENT" ,
"details" : {}
}
}
Always "failed" for failed generations
Error details with message (error code) and details fields (see Error Codes ) Always null for failed tasks
Sent when generation exceeds the 10-minute limit. {
"generation_id" : "ffd473d5-5bef-4e14-bf22-8d559be3c19f" ,
"status" : "failed" ,
"prompt" : "A cat playing with a ball in a sunny room" ,
"video_url" : null ,
"created_at" : "2025-12-06T21:43:03.374000" ,
"completed_at" : "2025-12-06T21:44:06.143000" ,
"seed" : 319790131 ,
"error" : {
"message" : "TIMEOUT" ,
"details" : {}
}
}
Credits are automatically refunded for timed-out tasks.
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
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
from flask import Flask, request, jsonify
import logging
app = Flask( __name__ )
logging.basicConfig( level = logging. INFO )
@app.route ( '/webhook' , methods = [ 'POST' ])
def runblob_webhook ():
try :
data = request.json
generation_id = data.get( 'generation_id' )
status = data.get( 'status' )
if status == 'completed' :
video_url = data.get( 'video_url' )
logging.info( f "Video ready for { generation_id } : { video_url } " )
# Process successful generation
process_video(generation_id, video_url)
else :
error = data.get( 'error' , {})
error_code = error.get( 'message' , 'Unknown error' )
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 ):
# Your video processing logic here
pass
def handle_failure ( generation_id , error_code ):
# Your error handling logic here
pass
Important: Always return a 200 status code quickly to acknowledge receipt. Perform heavy processing asynchronously to avoid timeouts.