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.
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,"credits_charged":300,"start_seconds":0,"end_seconds":5,"fps":30,"download":null}}
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()
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