Webhooks
Listen to template events and integrate with your application workflow
Get real-time notifications when users create, update, or delete templates in your embed. Perfect for syncing data, sending notifications, or triggering workflows.
Overview#
Webhooks send HTTP POST requests to your specified endpoint whenever:
- A user creates a new template (
template.create) - A user updates an existing template (
template.update) - A user deletes a template (
template.delete)
Setup#
1. Configure Webhook URL#
In your embed settings, add your webhook endpoint:
https://yourapp.com/api/studio-webhook2. Handle Webhook Events#
Create an endpoint that accepts POST requests:
// Next.js API Route
export async function POST(request) {
try {
const body = await request.json();
// Process the webhook
console.log("Webhook received:", body);
return Response.json({ success: true });
} catch (error) {
console.error("Webhook error:", error);
return Response.json({ error: error.message }, { status: 500 });
}
}Event Types#
template.create#
Fired when a user creates a new template:
{
"event_type": "template.create",
"template": {
"id": "abc123",
"name": "My New Design",
"workspace_id": "workspace_789",
"user_id": "user_456",
"canvas_data": {...},
"canvas_width": 1080,
"canvas_height": 1080,
"current_version": "1.0",
"thumbnail_url": "https://...",
"description": "Template description",
"pages_data": [
{
"id": "page_1",
"name": "Page 1",
"thumbnail_url": "https://...",
"canvas_data": {...}
}
],
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
},
"workspace_id": "workspace_789",
"user_id": "user_456",
"timestamp": "2024-01-15T10:30:00Z"
}template.update#
Fired when a user saves changes to a template:
{
"event_type": "template.update",
"template": {
"id": "abc123",
"name": "My Updated Design",
"workspace_id": "workspace_789",
"user_id": "user_456",
"canvas_data": {...},
"canvas_width": 1080,
"canvas_height": 1350,
"current_version": "1.1",
"thumbnail_url": "https://...",
"description": "Updated template description",
"pages_data": [
{
"id": "page_1",
"name": "Page 1",
"thumbnail_url": "https://...",
"canvas_data": {...}
},
{
"id": "page_2",
"name": "Page 2",
"thumbnail_url": "https://...",
"canvas_data": {...}
}
],
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T11:45:00Z"
},
"workspace_id": "workspace_789",
"user_id": "user_456",
"timestamp": "2024-01-15T11:45:00Z"
}template.delete#
Fired when a user deletes a template. The payload is lighter since the template data is no longer available:
{
"event_type": "template.delete",
"template": {
"id": "abc123",
"name": "My Template",
"workspace_id": "789"
},
"workspace_id": "789",
"user_id": "user_456",
"timestamp": "2024-01-15T12:00:00Z"
}Implementation Examples#
Express.js#
const express = require("express");
const app = express();
app.use(express.json());
app.post("/api/studio-webhook", (req, res) => {
const { event_type, template, workspace_id, user_id } = req.body;
console.log(`Event: ${event_type} for template ${template.id}`);
// Process the event
switch (event_type) {
case "template.create":
handleTemplateCreate(req.body);
break;
case "template.update":
handleTemplateUpdate(req.body);
break;
case "template.delete":
handleTemplateDelete(req.body);
break;
}
res.json({ success: true });
});
function handleTemplateCreate(data) {
// Save to database, send notification, etc.
console.log("New template created:", data.template.name);
console.log("Pages count:", data.template.pages_data?.length || 1);
}
function handleTemplateUpdate(data) {
// Update records, sync changes, etc.
console.log("Template updated:", data.template.name);
console.log(
"Canvas size:",
`${data.template.canvas_width}x${data.template.canvas_height}`,
);
}
function handleTemplateDelete(data) {
// Remove from database, notify users, etc.
console.log("Template deleted:", data.template.id);
}PHP#
<?php
header('Content-Type: application/json');
$input = file_get_contents('php://input');
$data = json_decode($input, true);
$event_type = $data['event_type'];
$template = $data['template'];
$workspace_id = $data['workspace_id'];
$user_id = $data['user_id'];
switch ($event_type) {
case 'template.create':
handleTemplateCreate($data);
break;
case 'template.update':
handleTemplateUpdate($data);
break;
case 'template.delete':
handleTemplateDelete($data);
break;
}
function handleTemplateDelete($data) {
error_log("Template deleted: " . $data['template']['id']);
}
function handleTemplateCreate($data) {
// Database insert, notification, etc.
error_log("New template: " . $data['template']['name']);
error_log("Template ID: " . $data['template']['id']);
}
function handleTemplateUpdate($data) {
// Database update, sync, etc.
error_log("Template updated: " . $data['template']['name']);
error_log("Version: " . $data['template']['current_version']);
}
echo json_encode(['success' => true]);
?>Python (FastAPI)#
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional, Dict, Any
app = FastAPI()
class PageData(BaseModel):
id: str
name: str
thumbnail_url: Optional[str]
canvas_data: Dict[str, Any]
class Template(BaseModel):
id: str
name: str
workspace_id: str
user_id: str
canvas_data: Dict[str, Any]
canvas_width: int
canvas_height: int
current_version: str
thumbnail_url: Optional[str]
description: Optional[str]
pages_data: Optional[List[PageData]]
created_at: str
updated_at: str
class WebhookPayload(BaseModel):
event_type: str
template: Template
workspace_id: str
user_id: str
timestamp: str
@app.post("/api/studio-webhook")
async def webhook_handler(payload: WebhookPayload):
print(f"Event: {payload.event_type} for template {payload.template.id}")
if payload.event_type == "template.create":
await handle_template_create(payload)
elif payload.event_type == "template.update":
await handle_template_update(payload)
elif payload.event_type == "template.delete":
await handle_template_delete(payload)
return {"success": True}
async def handle_template_create(data):
# Process new template
print(f"New template created: {data.template.name}")
print(f"Canvas size: {data.template.canvas_width}x{data.template.canvas_height}")
async def handle_template_update(data):
# Process template update
print(f"Template updated: {data.template.name}")
print(f"Pages count: {len(data.template.pages_data) if data.template.pages_data else 1}")
async def handle_template_delete(data):
# Remove template from records
print(f"Template deleted: {data.template.id}")Common Use Cases#
Save to Database#
async function handleTemplateCreate(data) {
await db.templates.create({
id: data.template.id,
name: data.template.name,
user_id: data.user_id,
workspace_id: data.workspace_id,
canvas_width: data.template.canvas_width,
canvas_height: data.template.canvas_height,
pages_count: data.template.pages_data?.length || 1,
thumbnail_url: data.template.thumbnail_url,
current_version: data.template.current_version,
created_at: data.template.created_at,
updated_at: data.template.updated_at,
});
}Send Notifications#
async function handleTemplateUpdate(data) {
// Email notification
await sendEmail({
to: await getUserEmail(data.user_id),
subject: "Template Updated",
body: `Your template "${data.template.name}" has been updated.`,
});
// Slack notification
await sendSlackMessage({
channel: "#designs",
text: `Template "${data.template.name}" was updated by user ${data.user_id}`,
});
}Sync with External API#
async function handleTemplateCreate(data) {
// Sync with external design management system
await externalAPI.createDesign({
id: data.template.id,
name: data.template.name,
creator: data.user_id,
workspace: data.workspace_id,
thumbnail: data.template.thumbnail_url,
canvas_size: `${data.template.canvas_width}x${data.template.canvas_height}`,
pages: data.template.pages_data?.length || 1,
});
}Security#
Verify Webhook Origin#
Always verify webhooks come from Orshot:
app.post("/api/studio-webhook", (req, res) => {
const origin = req.get("Origin") || req.get("Referer");
if (!origin || !origin.includes("orshot.com")) {
return res.status(403).json({ error: "Forbidden" });
}
// Process webhook...
});Use HTTPS#
Always use HTTPS endpoints for webhooks in production:
✅ https://yourapp.com/api/studio-webhook
❌ http://yourapp.com/api/studio-webhookValidate Payload#
Check that required fields are present:
function validateWebhook(body) {
const required = [
"event_type",
"template",
"workspace_id",
"user_id",
"timestamp",
];
for (const field of required) {
if (!body[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
if (
!["template.create", "template.update", "template.delete"].includes(
body.event_type,
)
) {
throw new Error(`Invalid event type: ${body.event_type}`);
}
// Validate template object
const templateRequired = ["id", "name", "workspace_id", "user_id"];
for (const field of templateRequired) {
if (!body.template[field]) {
throw new Error(`Missing template field: ${field}`);
}
}
}Error Handling#
Retry Logic#
Handle temporary failures gracefully:
app.post("/api/studio-webhook", async (req, res) => {
try {
await processWebhook(req.body);
res.json({ success: true });
} catch (error) {
console.error("Webhook processing failed:", error);
// Return 5xx status for retries
res.status(500).json({
error: "Processing failed",
retry: true,
});
}
});Logging#
Log all webhook events for debugging:
function logWebhook(data) {
console.log(
JSON.stringify({
timestamp: new Date().toISOString(),
event_type: data.event_type,
template_id: data.template_id,
user_id: data.user_data?.user_id,
success: true,
}),
);
}Testing#
Local Development#
Use ngrok to expose your local server:
# Install ngrok
npm install -g ngrok
# Expose your local server
ngrok http 3000
# Use the HTTPS URL in your webhook settings
https://abc123.ngrok.io/api/studio-webhookTest Payloads#
Create test webhook payloads for development:
const testPayload = {
event_type: "template.create",
template: {
id: "test_123",
name: "Test Template",
workspace_id: "test_workspace",
user_id: "test_user",
canvas_data: {},
canvas_width: 1080,
canvas_height: 1080,
current_version: "1.0",
thumbnail_url: "https://example.com/thumb.jpg",
description: "Test template description",
pages_data: [
{
id: "page_1",
name: "Page 1",
thumbnail_url: "https://example.com/page1.jpg",
canvas_data: {},
},
],
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
},
workspace_id: "test_workspace",
user_id: "test_user",
timestamp: new Date().toISOString(),
};
// Send test webhook
fetch("http://localhost:3000/api/studio-webhook", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(testPayload),
});Troubleshooting#
Webhook Not Firing#
- Check if webhook URL is correctly configured
- Verify the endpoint is accessible from the internet
- Ensure you're returning a 2xx status code
Payload Issues#
- Log the raw request body to see what's being sent
- Check Content-Type headers
- Verify JSON parsing is working correctly
Timeout Issues#
- Keep webhook processing under 10 seconds
- Use background jobs for heavy processing
- Return success response quickly, process asynchronously
Ready to automate?
Start rendering images, PDFs and videos from your templates in under 2 minutes. Free plan, no credit card.
Get your API key- Image, PDF and video generation via API
- Visual editor with AI and smart layouts
- Zapier, Make, MCP and 50+ integrations
- White-label embed for your own app
- 60 free renders — no credit card required