Skip to main content
By the end of this guide, you’ll have:
  • ✅ Created a webhook endpoint in your application
  • ✅ Registered it with Magic Hour
  • ✅ Tested it with a real API call
  • ✅ Verified webhook delivery works

What Are Webhooks?

Webhooks let you receive real-time notifications when your API requests complete, eliminating the need for polling. Instead of repeatedly checking job status, Magic Hour automatically notifies your application when jobs finish. Benefits:
  • ✅ Real-time notifications (no polling delays)
  • ✅ Efficient resource usage (no constant polling)
  • ✅ Better user experience (instant updates)
  • ✅ Scalable for high-volume operations

Step 1: Create Your Webhook Endpoint

First, create a simple webhook handler that can receive and process events.
Using Jupyter/Colab? See the “Colab/Jupyter” tab below for notebook-compatible code, or use webhook.site for instant testing without any code.
from fastapi import FastAPI, Request
import json

app = FastAPI()

@app.post("/webhook")
async def webhook_handler(request: Request):
    # Get the event data
    event = await request.json()
    
    # Log the event for testing
    print(f"Received event: {event['type']}")
    print(f"Payload: {json.dumps(event['payload'], indent=2)}")
    
    # Handle different event types
    match event['type']:
        case 'video.started':
            print('🎬 Video processing started')
        case 'video.completed':
            print('✅ Video processing completed')
            # Download URL available in event['payload']['downloads']
        case 'video.errored':
            print('❌ Video processing failed')
        case 'image.completed':
            print('🖼️ Image processing completed')
        case 'image.errored':
            print('❌ Image processing failed')
    
    # Always return success
    return {"success": True}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Step 2: Make Your Endpoint Publicly Accessible

Your webhook endpoint needs to be accessible from the internet. Choose one of these options:

Step 3: Register Your Webhook with Magic Hour

1

Visit Developer Hub

Go to Magic Hour Developer Hub, and click Create WebhookWebhook Table
2

Configure webhook

Enter your webhook details:
  • Endpoint URL: Your public HTTPS URL (e.g., https://abc123.ngrok.io/webhook)
  • Events: Select the events you want to receive:
    • video.started - When video processing begins
    • video.completed - When video is ready for download
    • video.errored - When video processing fails
    • image.completed - When image is ready for download
    • image.errored - When image processing fails
    • audio.completed - When audio is ready for download
    • audio.errored - When audio processing fails Create webhook modal
Click Create Webhook
3

Save webhook secret

Important: Copy and save the webhook secret - you’ll need this for security verification later.Webhook Secret
Store this secret securely! It’s used to verify that webhooks are actually from Magic Hour.

Step 4: Test Your Webhook End-to-End

Now let’s verify everything works by making a real API call and watching for the webhook.
1

Start monitoring your webhook

If using your own server: Watch the console logs
# Your server should show:
🚀 Webhook server running on http://localhost:8000
If using Colab/Jupyter: Watch the cell output for webhook eventsIf using webhook.site: Keep the browser tab open to see incoming requests in real-time
2

Make a test API call

Create a simple image to trigger webhook events:
from magic_hour import Client

client = Client(token="your-api-key")

# Create a simple AI image - this will trigger webhooks
result = client.v1.ai_image_generator.generate(
    image_count=1,
    orientation="square",
    style={"prompt": "A cute cat wearing sunglasses", "tool": "ai-anime-generator"}
)

print(f"Job created! ID: {result.id}")
print("Watch your webhook endpoint for events...")

# In Colab, you'll see the webhook events appear in the cell output above
3

Verify webhook delivery

Within seconds, you should see webhook events in your console or webhook.site:
{
  "type": "image.completed",
  "payload": {
    "id": "clx7uu86w0a5qp55yxz315r6r",
    "status": "complete",
    "downloads": [
      {
        "url": "https://images.magichour.ai/id/output.png",
        "expires_at": "2024-10-19T05:16:19.027Z"
      }
    ]
  }
}
Success! 🎉 Your webhook is working end-to-end.

Understanding Webhook Retries

If your endpoint doesn’t respond with a 2xx status code, Magic Hour will retry delivery:
  • Duration: Up to 24 hours
  • Pattern: Exponential backoff (1s, 2s, 4s, 8s, …)
  • After 24 hours: Event marked as failed, no more retries
Disabled Webhooks: If a webhook is disabled, pending events are skipped and marked as failed after 24 hours.

Best Practices for Reliable Webhooks

Do:
  • Return 2xx status codes within 10 seconds
  • Process events asynchronously (respond fast, process later)
  • Implement idempotent processing (handle duplicate events)
  • Log all webhook events for debugging
Don’t:
  • Perform long-running operations before responding
  • Return non-2xx codes for successful receipt
  • Assume events are delivered exactly once
  • Block the response while processing

Production Webhook Handler

For production, implement robust error handling and background processing:
from fastapi import FastAPI, Request, BackgroundTasks
import logging

app = FastAPI()
logger = logging.getLogger(__name__)

@app.post("/webhook")
async def webhook_handler(request: Request, background_tasks: BackgroundTasks):
    try:
        event = await request.json()
        
        # Log the event
        logger.info(f"Received webhook: {event['type']}", extra={
            'job_id': event['payload'].get('id'),
            'status': event['payload'].get('status')
        })
        
        # Respond immediately
        background_tasks.add_task(process_webhook_event, event)
        return {"success": True}
        
    except Exception as e:
        logger.error(f"Webhook error: {e}")
        return {"error": "Internal error"}, 500

async def process_webhook_event(event):
    """Process webhook in background"""
    try:
        event_type = event['type']
        payload = event['payload']
        
        if event_type == 'video.completed':
            # Update database
            await update_job_status(payload['id'], 'completed')
            # Notify user
            await notify_user_completion(payload)
            
        elif event_type == 'video.errored':
            # Handle error
            await update_job_status(payload['id'], 'failed')
            await notify_user_error(payload)
            
    except Exception as e:
        logger.error(f"Background processing failed: {e}")

Testing Locally

Before registering with Magic Hour, test your handler locally:
# Start your server
python webhook_server.py  # or node webhook_server.js

# In another terminal, send a test event
curl -X POST http://localhost:8000/webhook \
  -H "Content-Type: application/json" \
  -d '{
    "type": "video.completed",
    "payload": {
      "id": "test-job-123",
      "status": "complete",
      "downloads": [
        {
          "url": "https://videos.magichour.ai/test/output.mp4",
          "expires_at": "2024-12-01T12:00:00Z"
        }
      ]
    }
  }'
You should see the event logged in your server console.

Webhook Handler Requirements

Your webhook endpoint must:

1. Accept POST Requests

@app.post("/webhook")  # Must be POST
async def webhook_handler(request: Request):
    ...

2. Parse JSON Payload

event = await request.json()
# event = { "type": "...", "payload": {...} }

3. Return 2xx Status Code

return {"success": True}  # Returns 200
# Magic Hour interprets this as successful delivery

4. Respond Within 10 Seconds

# ✅ Good: Respond fast, process later
background_tasks.add_task(process_event, event)
return {"success": True}

# ❌ Bad: Long processing blocks response
process_event_synchronously(event)  # This might timeout!
return {"success": True}

Troubleshooting

Webhook not receiving events?
  • ✅ Check your endpoint URL is publicly accessible
  • ✅ Ensure your server returns HTTP 2xx status codes
  • ✅ Verify the webhook is enabled in Developer Hub
  • ✅ Check server logs for errors
  • ✅ Test with ngrok or webhook.site first
Colab/Jupyter specific issues:
  • ✅ Install required packages: !pip install fastapi uvicorn nest-asyncio pyngrok
  • ✅ Make sure the server thread started successfully
  • ✅ Check if ngrok tunnel is active and accessible
  • ✅ Try webhook.site as an alternative for quick testing
AsyncIO errors in notebooks?
  • ✅ Use the Colab/Jupyter code version with nest_asyncio.apply()
  • ✅ Don’t run uvicorn.run() directly in notebooks - use the threading approach

Next Steps

Now that your webhook is working:
Need help? Join our Discord community or email [email protected]