Webhooks
Webhooks are automated, non-interactive messages sent from Frame.io to your application when specific events occur. They're perfect for building reactive workflows that respond to changes in real-time without user intervention.
Why Use Webhooks?
Webhooks are ideal when you need to:
- React to file changes (uploads, processing completion, etc.)
- Monitor project activity (comments, approvals, status changes)
- Trigger automated workflows (notifications, data processing, integrations)
- Keep external systems in sync with Frame.io events
- Build event-driven applications that respond to Frame.io activity
How Webhooks Work
- Event occurs in Frame.io (file uploaded, comment added, etc.)
- Frame.io sends HTTP POST to your configured webhook URL
- Your handler processes the event data
Webhook Decorator
Use the @app.on_webhook decorator to register handlers:
import os
# Single webhook - use default WEBHOOK_SECRET env var
@app.on_webhook(event_type="file.ready")
async def on_file_ready(event: WebhookEvent):
# Handle the event
pass
# Multiple webhooks with different secrets - use explicit env vars
@app.on_webhook(event_type="file.ready", secret=os.environ["FILE_READY_WEBHOOK_SECRET"])
async def on_file_ready(event: WebhookEvent):
pass
@app.on_webhook(event_type="comment.created", secret=os.environ["COMMENTS_WEBHOOK_SECRET"])
async def on_comment_created(event: WebhookEvent):
pass
Parameters
event_type(str | list[str]): The event name(s) to listen forsecret(str | None, optional): The signing secret from Frame.io. If not provided, falls back to theWEBHOOK_SECRETenvironment variable. Explicit parameter takes precedence over environment variable.
Environment Variables
Single webhook: Use the default WEBHOOK_SECRET environment variable and omit the secret parameter.
Multiple webhooks with different secrets: Pass each secret explicitly via secret=os.environ["WEBHOOK_NAME_WEBHOOK_SECRET"] to keep secrets out of your code while supporting multiple webhook configurations.
Webhook Event Object
The WebhookEvent object provides typed access to all event data:
from frameio_kit import WebhookEvent
async def handler(event: WebhookEvent):
print(event.type) # "file.ready"
print(event.resource_id) # "abc123"
print(event.account_id) # "acc_456"
print(event.user_id) # "user_789"
print(event.project_id) # "proj_101"
print(event.workspace_id) # "ws_123"
Event Types
See Webhook Event Subscriptions for the full list of event types.
Example 1: Single Webhook (Default Env Var)
from frameio_kit import App, WebhookEvent, Message
app = App()
# Single webhook - WEBHOOK_SECRET env var used automatically
@app.on_webhook("file.ready")
async def on_file_ready(event: WebhookEvent):
print(f"File {event.resource_id} is ready for processing")
# Simulate some processing
await process_file(event.resource_id)
async def process_file(file_id: str):
# Your processing logic here
pass
Example 2: Multiple Webhooks (Explicit Env Vars)
import os
from frameio_kit import App, WebhookEvent
app = App()
# Multiple webhooks with different secrets - use explicit env vars
@app.on_webhook(event_type="file.ready", secret=os.environ["FILE_READY_WEBHOOK_SECRET"])
async def on_file_ready(event: WebhookEvent):
print(f"File ready: {event.resource_id}")
await process_file(event.resource_id)
@app.on_webhook(event_type="comment.created", secret=os.environ["COMMENT_CREATED_WEBHOOK_SECRET"])
async def on_comment_created(event: WebhookEvent):
print(f"New comment: {event.resource_id}")
await notify_team(event)
@app.on_webhook(event_type="comment.updated", secret=os.environ["COMMENT_UPDATED_WEBHOOK_SECRET"])
async def on_comment_updated(event: WebhookEvent):
print(f"Updated comment: {event.resource_id}")
await notify_team(event)
Example 3: Using the API Client
import os
from frameio import CreateCommentParamsData
from frameio_kit import App, WebhookEvent, Message
app = App(token=os.getenv("FRAMEIO_TOKEN"))
# Single webhook - use default WEBHOOK_SECRET
@app.on_webhook("file.ready")
async def add_processing_comment(event: WebhookEvent):
# Use the API client to add a comment back to Frame.io
await app.client.comments.create(
account_id=event.account_id,
file_id=event.resource_id,
data=CreateCommentParamsData(text="File has been automatically processed!")
)
Dynamic Secret Resolution
When you need to resolve webhook secrets dynamically (e.g., from a database for multi-tenant applications), use secret resolvers.
Decorator-Level Resolver
Provide an async function that receives the WebhookEvent and returns the secret:
from frameio_kit import App, WebhookEvent
app = App()
async def resolve_webhook_secret(event: WebhookEvent) -> str:
"""Resolve secret based on account ID."""
return await db.get_webhook_secret(account_id=event.account_id)
# Use the resolver for this specific webhook
@app.on_webhook("file.ready", secret=resolve_webhook_secret)
async def on_file_ready(event: WebhookEvent):
print(f"File {event.resource_id} is ready")
App-Level Resolver
For centralized secret management across all webhooks, use the app-level resolver. See App Configuration for details:
from frameio_kit import App, SecretResolver, WebhookEvent, ActionEvent
class MySecretResolver:
async def get_webhook_secret(self, event: WebhookEvent) -> str:
return await db.get_webhook_secret(event.account_id)
async def get_action_secret(self, event: ActionEvent) -> str:
return await db.get_action_secret(event.account_id)
app = App(secret_resolver=MySecretResolver())
# All webhooks use the app-level resolver by default
@app.on_webhook("file.ready")
async def on_file_ready(event: WebhookEvent):
pass
Secret Resolution Precedence
- Explicit string secret (
secret="...") - Decorator-level resolver (
secret=my_resolver) - App-level resolver (
App(secret_resolver=...)) - Environment variable (
WEBHOOK_SECRET)
Setting Up Webhooks in Frame.io
See Webhook Tutorial for instructions on how to set up webhooks in Frame.io.
Best Practices
- Handle errors gracefully - Webhook failures can cause retries
- Keep handlers fast - Long-running operations should be queued, Frame.io expects a response within 5 seconds
- Handle retries - Non-2xx or slow responses will cause Frame.io to retry the request up to 5 times (initial + 4 retries)
- Log webhook events for debugging and monitoring
Security Considerations
- Use HTTPS for your webhook endpoints
- Validate event data before processing
- Implement rate limiting to prevent abuse
- Monitor for suspicious activity and unexpected payloads