Event Structure
All webhook events follow a consistent structure with two main fields:
{
"type": "event.name",
"payload": {
// Event-specific data matching API response format
}
}
Event Naming Convention
Events use dot notation: {resource}.{action}
- Resource:
video, image, audio
- Action:
started, completed, errored
The payload field contains the same data structure as the corresponding API endpoint:
Consistent Data: Webhook payloads use the exact same format as API responses, making it easy
to reuse your existing data handling code.
Video Events
Video events are triggered during the lifecycle of video processing jobs. All payloads match the GET /v1/video-projects/:id response format.
video.started
When triggered: Video processing begins (job moves from pending to rendering)
Use cases:
- Update UI to show “processing” status
- Log job start time for analytics
- Send user notification that processing began
Key payload fields:
{
"type": "video.started",
"payload": {
"id": "clx7uu86w0a5qp55yxz315r6r",
"status": "rendering",
"type": "ANIMATION",
"created_at": "2024-10-19T05:10:19.027Z",
"width": 512,
"height": 960,
"total_frame_cost": 450,
"credits_charged": 450,
"downloads": [],
"error": null
}
}
Processing Time: Video processing can take 30 seconds to several minutes depending on length
and complexity.
video.completed
When triggered: Video processing finishes successfully
Use cases:
- Download the completed video
- Update database with completion status
- Send user notification with download link
- Trigger downstream workflows
Key payload fields:
{
"type": "video.completed",
"payload": {
"id": "clx7uu86w0a5qp55yxz315r6r",
"status": "complete",
"type": "ANIMATION",
"downloads": [
{
"url": "https://videos.magichour.ai/clx7uu86w0a5qp55yxz315r6r/output.mp4",
"expires_at": "2024-10-19T05:16:19.027Z"
}
],
"error": null,
"completed_at": "2024-10-19T05:15:45.123Z"
}
}
Download URLs Expire: Video download URLs typically expire after 24 hours. Download and store
videos promptly or use the API to get fresh URLs.
video.errored
When triggered: Video processing fails due to an error
Use cases:
- Log error details for debugging
- Retry processing if appropriate
- Notify user of failure
- Update job status in database
Key payload fields:
{
"type": "video.errored",
"payload": {
"id": "clx7uu86w0a5qp55yxz315r6r",
"status": "error",
"downloads": [],
"error": {
"code": "invalid_video_file",
"message": "The video file contains invalid data. Please try a different file."
},
"failed_at": "2024-10-19T05:12:30.456Z"
}
}
Common error codes:
invalid_video_file - Corrupted or unsupported video format
file_too_large - Video file exceeds size limits
insufficient_credits - Not enough credits to process
invalid_parameters - Invalid width, height, or other parameters
processing_timeout - Processing took too long and was terminated
No Charges: You’re never charged credits when processing fails with an error.
Image Events
Image events track the lifecycle of image processing jobs. All payloads match the GET /v1/image-projects/:id response format.
image.started
When triggered: Image processing begins (job moves from pending to rendering)
Use cases:
- Show processing indicator in UI
- Track processing start time
- Update job status in database
Key payload fields:
{
"type": "image.started",
"payload": {
"id": "clx8abc123def456ghi789",
"status": "rendering",
"type": "AI_IMAGE_GENERATOR",
"created_at": "2024-10-19T05:10:19.027Z",
"width": 1024,
"height": 1024,
"credits_charged": 10,
"downloads": [],
"error": null
}
}
Fast Processing: Some image modes (like AI Image Generator) process so quickly that
image.started events may not be sent. Always handle cases where you only receive
image.completed.
image.completed
When triggered: Image processing finishes successfully
Use cases:
- Download the generated image
- Display result to user
- Update completion status
- Trigger post-processing workflows
Key payload fields:
{
"type": "image.completed",
"payload": {
"id": "clx8abc123def456ghi789",
"status": "complete",
"type": "AI_IMAGE_GENERATOR",
"downloads": [
{
"url": "https://images.magichour.ai/clx8abc123def456ghi789/output.png",
"expires_at": "2024-10-19T05:16:19.027Z"
}
],
"error": null,
"completed_at": "2024-10-19T05:10:25.789Z"
}
}
Fast Processing: Most image operations complete within 5-30 seconds, much faster than video
processing.
image.errored
When triggered: Image processing fails due to an error
Use cases:
- Display error message to user
- Log error for debugging
- Retry with different parameters
- Update job status
Key payload fields:
{
"type": "image.errored",
"payload": {
"id": "clx8abc123def456ghi789",
"status": "error",
"downloads": [],
"error": {
"code": "no_source_face",
"message": "Please use an image with a detectable face"
},
"failed_at": "2024-10-19T05:10:22.123Z"
}
}
Common error codes:
no_source_face - Face required but not detected in image
invalid_file_format - Unsupported image format
file_too_large - Image file exceeds size limits
insufficient_credits - Not enough credits for operation
invalid_parameters - Invalid dimensions or other parameters
content_policy_violation - Image violates content guidelines
No Charges: Failed image processing never consumes credits.
Audio Events
Audio events track the lifecycle of audio processing jobs. All payloads match the GET /v1/audio-projects/:id response format.
audio.started
When triggered: Audio processing begins
Key payload fields:
{
"type": "audio.started",
"payload": {
"id": "clx9audio123voice456",
"status": "rendering",
"type": "AI_VOICE_GENERATOR",
"created_at": "2024-10-19T05:10:19.027Z",
"credits_charged": 5,
"downloads": [],
"error": null
}
}
audio.completed
When triggered: Audio processing finishes successfully
Key payload fields:
{
"type": "audio.completed",
"payload": {
"id": "clx9audio123voice456",
"status": "complete",
"downloads": [
{
"url": "https://audio.magichour.ai/clx9audio123voice456/output.mp3",
"expires_at": "2024-10-19T05:16:19.027Z"
}
],
"error": null,
"completed_at": "2024-10-19T05:10:35.456Z"
}
}
audio.errored
When triggered: Audio processing fails
Common error codes:
text_too_long - Input text exceeds maximum length
invalid_voice - Requested voice not available
insufficient_credits - Not enough credits
processing_timeout - Audio generation took too long
Event Handling Patterns
Basic Event Router
async def handle_webhook_event(event):
event_type = event['type']
payload = event['payload']
match event_type:
# Video events
case 'video.started':
await handle_video_started(payload)
case 'video.completed':
await handle_video_completed(payload)
case 'video.errored':
await handle_video_errored(payload)
# Image events
case 'image.started':
await handle_image_started(payload)
case 'image.completed':
await handle_image_completed(payload)
case 'image.errored':
await handle_image_errored(payload)
# Audio events
case 'audio.started':
await handle_audio_started(payload)
case 'audio.completed':
await handle_audio_completed(payload)
case 'audio.errored':
await handle_audio_errored(payload)
case _:
print(f"Unknown event type: {event_type}")
Database Update Pattern
async def handle_completion_event(event):
payload = event['payload']
job_id = payload['id']
# Update job status in database
await db.execute(
"UPDATE jobs SET status = ?, download_url = ?, completed_at = ? WHERE magic_hour_id = ?",
(payload['status'], payload['downloads'][0]['url'], payload['completed_at'], job_id)
)
# Notify user
await notify_user_completion(job_id)
Error Handling Pattern
async def handle_error_event(event):
payload = event['payload']
error = payload['error']
# Log error details
logger.error(f"Job {payload['id']} failed", extra={
'job_id': payload['id'],
'error_code': error['code'],
'error_message': error['message'],
'job_type': payload['type']
})
# Update database
await db.execute(
"UPDATE jobs SET status = 'failed', error_code = ?, error_message = ? WHERE magic_hour_id = ?",
(error['code'], error['message'], payload['id'])
)
# Notify user of failure
await notify_user_error(payload['id'], error)
Next Steps