Orshot Logo
OrshotDocs
Orshot Embed

Webhooks

Listen to template events and integrate with your application workflow

Get real-time notifications when users create or update 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)

Setup

1. Configure Webhook URL

In your embed settings, add your webhook endpoint:

https://yourapp.com/api/studio-webhook

2. 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"
}

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;
  }
 
  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}`
  );
}

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;
}
 
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)
 
    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}")

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-webhook

Validate 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"].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-webhook

Test 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