Get real-time notifications when users create, update, or delete templates in your embed. Perfect for syncing data, sending notifications, or triggering workflows.
Webhooks send HTTP POST requests to your specified endpoint whenever:
template.create)template.update)template.delete)In your embed settings, add your webhook endpoint:
https://yourapp.com/api/studio-webhookCreate 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 });
}
}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"
}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"
}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"
}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
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]);
?>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}")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,
});
}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}`,
});
}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,
});
}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...
});Always use HTTPS endpoints for webhooks in production:
✅ https://yourapp.com/api/studio-webhook
❌ http://yourapp.com/api/studio-webhookCheck 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}`);
}
}
}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,
});
}
});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,
}),
);
}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-webhookCreate 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),
});