> ## Documentation Index
> Fetch the complete documentation index at: https://docs.magichour.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook Event Types

> Complete reference of all webhook events, payloads, and when they're triggered.

## Event Structure

All webhook events follow a consistent structure with two main fields:

```json theme={null}
{
  "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`

### Payload Format

The `payload` field contains the same data structure as the corresponding API endpoint:

* **Video events**: Match [`GET /v1/video-projects/:id`](/api-reference/video-projects/get-video-details)
* **Image events**: Match [`GET /v1/image-projects/:id`](/api-reference/image-projects/get-image-details)
* **Audio events**: Match [`GET /v1/audio-projects/:id`](/api-reference/audio-projects/get-audio-details)

<Info>
  **Consistent Data**: Webhook payloads use the exact same format as API responses, making it easy
  to reuse your existing data handling code.
</Info>

## Video Events

Video events are triggered during the lifecycle of video processing jobs. All payloads match the [`GET /v1/video-projects/:id`](/api-reference/video-projects/get-video-details) 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**:

```json theme={null}
{
  "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
  }
}
```

<Note>
  **Processing Time**: Video processing can take 30 seconds to several minutes depending on length
  and complexity.
</Note>

### `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**:

```json theme={null}
{
  "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"
  }
}
```

<Warning>
  **Download URLs Expire**: Video download URLs typically expire after 24 hours. Download and store
  videos promptly or use the API to get fresh URLs.
</Warning>

### `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**:

```json theme={null}
{
  "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

<Info>**No Charges**: You're never charged credits when processing fails with an error.</Info>

## Image Events

Image events track the lifecycle of image processing jobs. All payloads match the [`GET /v1/image-projects/:id`](/api-reference/image-projects/get-image-details) 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**:

```json theme={null}
{
  "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
  }
}
```

<Warning>
  **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`.
</Warning>

### `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**:

```json theme={null}
{
  "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"
  }
}
```

<Info>
  **Fast Processing**: Most image operations complete within 5-30 seconds, much faster than video
  processing.
</Info>

### `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**:

```json theme={null}
{
  "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

<Info>**No Charges**: Failed image processing never consumes credits.</Info>

## Audio Events

Audio events track the lifecycle of audio processing jobs. All payloads match the [`GET /v1/audio-projects/:id`](/api-reference/audio-projects/get-audio-details) response format.

### `audio.started`

**When triggered**: Audio processing begins

**Key payload fields**:

```json theme={null}
{
  "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**:

```json theme={null}
{
  "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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

<CardGroup cols={2}>
  <Card title="Webhook Quickstart" icon="rocket" href="/integration/webhook/overview">
    Set up your first webhook with end-to-end testing
  </Card>

  <Card title="Secure Your Handler" icon="shield" href="/integration/webhook/secure-handler">
    Add signature verification for production security
  </Card>

  <Card title="Production Deployment" icon="server" href="/integration/webhook/create-handler">
    Deploy robust webhook handlers to production
  </Card>

  <Card title="API Reference" icon="webhook" href="/webhook-reference">
    Complete webhook API documentation
  </Card>
</CardGroup>
