Headers

Events includes two headers to help verify the authenticity of the payload.

magic-hour-event-signature

A signatured created with the webhook secret key and a signed_payload, using HMAC with SHA-256

magic-hour-event-signature: d1747eb9491aadeac6173f1c9fd5f4aff87c8981ac53d87b0830199221ff50af

magic-hour-event-timestamp

Time in seconds since the epoch. Use this value to check whether the request is within a reasonable window of the current time. Usually less than 5 minutes.

magic-hour-event-timestamp: 1729314984
HTTP headers are case insensitive

Using the header to verify the request ensures that the request is authentic. However, the header verification is not required to receive webhook events.

Compute the signature

To verify the request, generate the signature in your application and confirm that it matches the request.

1

Create signed payload

Create a signed_payload by concatenating:

  • the timestamp as a string
  • the character .
  • the payload JSON (stringified)

For example,

1729314984.{"type":"video.started","object":{"id":"cuid","name":"Video","status":"rendering","type":"TEXT_TO_VIDEO","created_at":"2024-10-19T05:16:19.027Z","width":720,"height":1280,"total_frame_cost":300,"start_seconds":0,"end_seconds":5,"fps":30,"download":null}}
2

Compute the signature

Compute an HMAC with the SHA256 Hash function. Using the webhook secret as the key, and use the signed_payload as the message.

import hmac
import hashlib
import os

webhook_secret = os.getenv("MAGIC_HOUR_WEBHOOK_SECRET")

def compute_signature(signed_payload: str) -> str:
    return hmac.new(
        webhook_secret,
        payload,
        hashlib.sha256
    ).hexdigest()
3

Compare against request header

Compare the computed signature value against the header value and ensure they are identical. And verify that the timestamp is within a reasonable window.

Integrate with handler

from fastapi import FastAPI, Request, HTTPException
import json

app = FastAPI()

@app.post("/webhook")
async def webhook(request: Request):
    # Get signature and timestamp from headers
    signature = request.headers.get('magic-hour-event-signature')
    timestamp = request.headers.get('magic-hour-event-timestamp')
    event = await request.json()

    # Verify timestamp is recent (within 5 minutes)
    if abs(int(time.time()) - int(timestamp) > 300
        raise HTTPException(status_code=401, detail="Timestamp too old")

    # Create signed payload and verify signature
    signed_payload = f"{timestamp}.{json.dumps(event.payload)}"
    if signature != compute_signature(signature):
        raise HTTPException(status_code=401, detail="Invalid signature")
    # rest of webhook logic