@editframe/create 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +11 -0
  2. package/dist/index.js +16 -28
  3. package/dist/index.js.map +1 -1
  4. package/dist/skills/editframe-brand-video-generator/README.md +155 -0
  5. package/dist/skills/editframe-brand-video-generator/SKILL.md +207 -0
  6. package/dist/skills/editframe-brand-video-generator/references/brand-examples.md +178 -0
  7. package/dist/skills/editframe-brand-video-generator/references/color-psychology.md +227 -0
  8. package/dist/skills/editframe-brand-video-generator/references/composition-patterns.md +383 -0
  9. package/dist/skills/editframe-brand-video-generator/references/editing.md +66 -0
  10. package/dist/skills/editframe-brand-video-generator/references/emotional-arcs.md +496 -0
  11. package/dist/skills/editframe-brand-video-generator/references/genre-selection.md +135 -0
  12. package/dist/skills/editframe-brand-video-generator/references/transition-styles.md +611 -0
  13. package/dist/skills/editframe-brand-video-generator/references/typography-personalities.md +326 -0
  14. package/dist/skills/editframe-brand-video-generator/references/video-archetypes.md +86 -0
  15. package/dist/skills/editframe-brand-video-generator/references/video-fundamentals.md +169 -0
  16. package/dist/skills/editframe-brand-video-generator/references/visual-metaphors.md +50 -0
  17. package/dist/skills/editframe-composition/SKILL.md +169 -0
  18. package/dist/skills/editframe-composition/references/audio.md +483 -0
  19. package/dist/skills/editframe-composition/references/captions.md +844 -0
  20. package/dist/skills/editframe-composition/references/composition-model.md +73 -0
  21. package/dist/skills/editframe-composition/references/configuration.md +403 -0
  22. package/dist/skills/editframe-composition/references/css-parts.md +105 -0
  23. package/dist/skills/editframe-composition/references/css-variables.md +640 -0
  24. package/dist/skills/editframe-composition/references/entry-points.md +810 -0
  25. package/dist/skills/editframe-composition/references/events.md +499 -0
  26. package/dist/skills/editframe-composition/references/getting-started.md +259 -0
  27. package/dist/skills/editframe-composition/references/hooks.md +234 -0
  28. package/dist/skills/editframe-composition/references/image.md +241 -0
  29. package/dist/skills/editframe-composition/references/r3f.md +580 -0
  30. package/dist/skills/editframe-composition/references/render-api.md +484 -0
  31. package/dist/skills/editframe-composition/references/render-strategies.md +119 -0
  32. package/dist/skills/editframe-composition/references/render-to-video.md +1101 -0
  33. package/dist/skills/editframe-composition/references/scripting.md +606 -0
  34. package/dist/skills/editframe-composition/references/sequencing.md +116 -0
  35. package/dist/skills/editframe-composition/references/server-rendering.md +753 -0
  36. package/dist/skills/editframe-composition/references/surface.md +329 -0
  37. package/dist/skills/editframe-composition/references/text.md +627 -0
  38. package/dist/skills/editframe-composition/references/time-model.md +99 -0
  39. package/dist/skills/editframe-composition/references/timegroup-modes.md +102 -0
  40. package/dist/skills/editframe-composition/references/timegroup.md +457 -0
  41. package/dist/skills/editframe-composition/references/timeline-root.md +398 -0
  42. package/dist/skills/editframe-composition/references/transcription.md +47 -0
  43. package/dist/skills/editframe-composition/references/transitions.md +608 -0
  44. package/dist/skills/editframe-composition/references/use-media-info.md +357 -0
  45. package/dist/skills/editframe-composition/references/video.md +506 -0
  46. package/dist/skills/editframe-composition/references/waveform.md +327 -0
  47. package/dist/skills/editframe-editor-gui/SKILL.md +152 -0
  48. package/dist/skills/editframe-editor-gui/references/active-root-temporal.md +657 -0
  49. package/dist/skills/editframe-editor-gui/references/canvas.md +947 -0
  50. package/dist/skills/editframe-editor-gui/references/controls.md +366 -0
  51. package/dist/skills/editframe-editor-gui/references/dial.md +756 -0
  52. package/dist/skills/editframe-editor-gui/references/editor-toolkit.md +587 -0
  53. package/dist/skills/editframe-editor-gui/references/filmstrip.md +460 -0
  54. package/dist/skills/editframe-editor-gui/references/fit-scale.md +772 -0
  55. package/dist/skills/editframe-editor-gui/references/focus-overlay.md +561 -0
  56. package/dist/skills/editframe-editor-gui/references/hierarchy.md +544 -0
  57. package/dist/skills/editframe-editor-gui/references/overlay-item.md +634 -0
  58. package/dist/skills/editframe-editor-gui/references/overlay-layer.md +429 -0
  59. package/dist/skills/editframe-editor-gui/references/pan-zoom.md +568 -0
  60. package/dist/skills/editframe-editor-gui/references/pause.md +397 -0
  61. package/dist/skills/editframe-editor-gui/references/play.md +370 -0
  62. package/dist/skills/editframe-editor-gui/references/preview.md +391 -0
  63. package/dist/skills/editframe-editor-gui/references/resizable-box.md +749 -0
  64. package/dist/skills/editframe-editor-gui/references/scrubber.md +588 -0
  65. package/dist/skills/editframe-editor-gui/references/thumbnail-strip.md +566 -0
  66. package/dist/skills/editframe-editor-gui/references/time-display.md +492 -0
  67. package/dist/skills/editframe-editor-gui/references/timeline-ruler.md +489 -0
  68. package/dist/skills/editframe-editor-gui/references/timeline.md +604 -0
  69. package/dist/skills/editframe-editor-gui/references/toggle-loop.md +618 -0
  70. package/dist/skills/editframe-editor-gui/references/toggle-play.md +526 -0
  71. package/dist/skills/editframe-editor-gui/references/transform-handles.md +924 -0
  72. package/dist/skills/editframe-editor-gui/references/trim-handles.md +725 -0
  73. package/dist/skills/editframe-editor-gui/references/workbench.md +453 -0
  74. package/dist/skills/editframe-motion-design/SKILL.md +101 -0
  75. package/dist/skills/editframe-motion-design/references/0-editframe.md +299 -0
  76. package/dist/skills/editframe-motion-design/references/1-intent.md +201 -0
  77. package/dist/skills/editframe-motion-design/references/2-physics-model.md +405 -0
  78. package/dist/skills/editframe-motion-design/references/3-attention.md +350 -0
  79. package/dist/skills/editframe-motion-design/references/4-process.md +418 -0
  80. package/dist/skills/editframe-vite-plugin/SKILL.md +75 -0
  81. package/dist/skills/editframe-vite-plugin/references/file-api.md +111 -0
  82. package/dist/skills/editframe-vite-plugin/references/getting-started.md +96 -0
  83. package/dist/skills/editframe-vite-plugin/references/jit-transcoding.md +91 -0
  84. package/dist/skills/editframe-vite-plugin/references/local-assets.md +75 -0
  85. package/dist/skills/editframe-vite-plugin/references/visual-testing.md +136 -0
  86. package/dist/skills/editframe-webhooks/SKILL.md +126 -0
  87. package/dist/skills/editframe-webhooks/references/events.md +382 -0
  88. package/dist/skills/editframe-webhooks/references/getting-started.md +232 -0
  89. package/dist/skills/editframe-webhooks/references/security.md +418 -0
  90. package/dist/skills/editframe-webhooks/references/testing.md +409 -0
  91. package/dist/skills/editframe-webhooks/references/troubleshooting.md +457 -0
  92. package/dist/templates/html/AGENTS.md +13 -0
  93. package/dist/templates/react/AGENTS.md +13 -0
  94. package/dist/utils.js +15 -16
  95. package/dist/utils.js.map +1 -1
  96. package/package.json +2 -2
  97. package/tsdown.config.ts +4 -0
  98. package/dist/detectAgent.js +0 -89
  99. package/dist/detectAgent.js.map +0 -1
@@ -0,0 +1,382 @@
1
+ ---
2
+ title: Webhook Events
3
+ description: Complete reference for all Editframe webhook event types, payload schemas, and the conditions that trigger each event.
4
+ type: reference
5
+ nav:
6
+ parent: "Events & Payloads"
7
+ priority: 2
8
+ ---
9
+
10
+ # Webhook Events
11
+
12
+ Complete reference of all webhook event types and their payload structures.
13
+
14
+ ## Event Structure
15
+
16
+ All webhook events follow this structure:
17
+
18
+ ```typescript
19
+ interface WebhookEvent<T> {
20
+ topic: string; // Event type (e.g., "render.completed")
21
+ data: T; // Event-specific payload
22
+ }
23
+ ```
24
+
25
+ The webhook is delivered as an HTTP POST request with:
26
+ - **Headers**: `Content-Type: application/json`, `X-Webhook-Signature: <hmac>`
27
+ - **Body**: JSON-encoded webhook event
28
+
29
+ ## Render Events
30
+
31
+ ### render.created
32
+
33
+ Triggered when a render job is created.
34
+
35
+ ```typescript
36
+ interface RenderCreatedPayload {
37
+ id: string; // Render UUID
38
+ status: "created"; // Always "created" for this event
39
+ created_at: string; // ISO 8601 timestamp
40
+ completed_at: null; // Not completed yet
41
+ failed_at: null; // Not failed yet
42
+ width: number; // Video width in pixels
43
+ height: number; // Video height in pixels
44
+ fps: number; // Frames per second
45
+ byte_size: null; // Not available until completed
46
+ duration_ms: null; // Not available until completed
47
+ md5: null; // Not available until completed
48
+ metadata: object | null; // Custom metadata from request
49
+ download_url: null; // Not available until completed
50
+ }
51
+ ```
52
+
53
+ **Example:**
54
+ ```json
55
+ {
56
+ "topic": "render.created",
57
+ "data": {
58
+ "id": "550e8400-e29b-41d4-a716-446655440000",
59
+ "status": "created",
60
+ "created_at": "2024-10-30T10:00:00Z",
61
+ "completed_at": null,
62
+ "failed_at": null,
63
+ "width": 1920,
64
+ "height": 1080,
65
+ "fps": 30,
66
+ "byte_size": null,
67
+ "duration_ms": null,
68
+ "md5": null,
69
+ "metadata": { "project_id": "abc123" },
70
+ "download_url": null
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### render.pending
76
+
77
+ Triggered when a render is queued for processing.
78
+
79
+ Payload structure is identical to `render.created`, with `status: "pending"`.
80
+
81
+ ### render.rendering
82
+
83
+ Triggered when a render begins active processing.
84
+
85
+ Payload structure is identical to `render.created`, with `status: "rendering"`.
86
+
87
+ ### render.completed
88
+
89
+ Triggered when a render successfully finishes.
90
+
91
+ ```typescript
92
+ interface RenderCompletedPayload {
93
+ id: string; // Render UUID
94
+ status: "complete"; // Always "complete" for this event
95
+ created_at: string; // ISO 8601 timestamp
96
+ completed_at: string; // ISO 8601 timestamp of completion
97
+ failed_at: null; // Not failed
98
+ width: number; // Video width in pixels
99
+ height: number; // Video height in pixels
100
+ fps: number; // Frames per second
101
+ byte_size: number; // File size in bytes
102
+ duration_ms: number; // Video duration in milliseconds
103
+ md5: string; // MD5 hash of video file
104
+ metadata: object | null; // Custom metadata from request
105
+ download_url: string; // Signed URL to download video
106
+ }
107
+ ```
108
+
109
+ **Example:**
110
+ ```json
111
+ {
112
+ "topic": "render.completed",
113
+ "data": {
114
+ "id": "550e8400-e29b-41d4-a716-446655440000",
115
+ "status": "complete",
116
+ "created_at": "2024-10-30T10:00:00Z",
117
+ "completed_at": "2024-10-30T10:01:23Z",
118
+ "failed_at": null,
119
+ "width": 1920,
120
+ "height": 1080,
121
+ "fps": 30,
122
+ "byte_size": 15728640,
123
+ "duration_ms": 5000,
124
+ "md5": "098f6bcd4621d373cade4e832627b4f6",
125
+ "metadata": { "project_id": "abc123" },
126
+ "download_url": "https://editframe.com/api/v1/renders/550e8400-e29b-41d4-a716-446655440000/mp4"
127
+ }
128
+ }
129
+ ```
130
+
131
+ **Usage:**
132
+ ```typescript
133
+ if (event.topic === "render.completed") {
134
+ const { id, download_url, duration_ms, byte_size } = event.data;
135
+
136
+ console.log(`Render ${id} completed:`);
137
+ console.log(`- Duration: ${duration_ms / 1000}s`);
138
+ console.log(`- Size: ${(byte_size / 1024 / 1024).toFixed(2)} MB`);
139
+
140
+ // Download the video
141
+ const response = await fetch(download_url);
142
+ const videoBuffer = await response.arrayBuffer();
143
+
144
+ // Save or upload to your storage
145
+ await saveVideo(id, videoBuffer);
146
+ }
147
+ ```
148
+
149
+ ### render.failed
150
+
151
+ Triggered when a render encounters an error.
152
+
153
+ ```typescript
154
+ interface RenderFailedPayload {
155
+ id: string; // Render UUID
156
+ status: "failed"; // Always "failed" for this event
157
+ created_at: string; // ISO 8601 timestamp
158
+ completed_at: null; // Not completed
159
+ failed_at: string; // ISO 8601 timestamp of failure
160
+ width: number; // Video width in pixels
161
+ height: number; // Video height in pixels
162
+ fps: number; // Frames per second
163
+ byte_size: null; // Not available
164
+ duration_ms: null; // Not available
165
+ md5: null; // Not available
166
+ metadata: object | null; // Custom metadata from request
167
+ download_url: null; // Not available
168
+ }
169
+ ```
170
+
171
+ **Example:**
172
+ ```json
173
+ {
174
+ "topic": "render.failed",
175
+ "data": {
176
+ "id": "550e8400-e29b-41d4-a716-446655440000",
177
+ "status": "failed",
178
+ "created_at": "2024-10-30T10:00:00Z",
179
+ "completed_at": null,
180
+ "failed_at": "2024-10-30T10:00:45Z",
181
+ "width": 1920,
182
+ "height": 1080,
183
+ "fps": 30,
184
+ "byte_size": null,
185
+ "duration_ms": null,
186
+ "md5": null,
187
+ "metadata": { "project_id": "abc123" },
188
+ "download_url": null
189
+ }
190
+ }
191
+ ```
192
+
193
+ ## File Events
194
+
195
+ ### file.created
196
+
197
+ Triggered when a file record is created (before upload).
198
+
199
+ ```typescript
200
+ interface FileCreatedPayload {
201
+ id: string; // File UUID
202
+ type: "video" | "image" | "caption"; // File type
203
+ status: "created"; // Always "created" for this event
204
+ filename: string; // Original filename
205
+ byte_size: null; // Not uploaded yet
206
+ md5: null; // Not uploaded yet
207
+ mime_type: null; // Not available yet
208
+ width: null; // Not available yet (images only)
209
+ height: null; // Not available yet (images only)
210
+ }
211
+ ```
212
+
213
+ **Example:**
214
+ ```json
215
+ {
216
+ "topic": "file.created",
217
+ "data": {
218
+ "id": "660e8400-e29b-41d4-a716-446655440001",
219
+ "type": "video",
220
+ "status": "created",
221
+ "filename": "my-video.mp4",
222
+ "byte_size": null,
223
+ "md5": null,
224
+ "mime_type": null,
225
+ "width": null,
226
+ "height": null
227
+ }
228
+ }
229
+ ```
230
+
231
+ ### file.uploading
232
+
233
+ Triggered when file upload is in progress.
234
+
235
+ Payload structure similar to `file.created`, with `status: "uploading"`.
236
+
237
+ ### file.processing
238
+
239
+ Triggered when a video file is being processed to ISOBMFF format.
240
+
241
+ Only sent for `type: "video"` files.
242
+
243
+ ```typescript
244
+ interface FileProcessingPayload {
245
+ id: string; // File UUID
246
+ type: "video"; // Always "video" for processing
247
+ status: "processing"; // Always "processing" for this event
248
+ filename: string; // Original filename
249
+ byte_size: number; // Uploaded file size
250
+ md5: string; // MD5 hash of uploaded file
251
+ mime_type: string; // MIME type (e.g., "video/mp4")
252
+ width: number | null; // Video width in pixels
253
+ height: number | null; // Video height in pixels
254
+ }
255
+ ```
256
+
257
+ ### file.ready
258
+
259
+ Triggered when a file is ready for use in compositions.
260
+
261
+ ```typescript
262
+ interface FileReadyPayload {
263
+ id: string; // File UUID
264
+ type: "video" | "image" | "caption"; // File type
265
+ status: "ready"; // Always "ready" for this event
266
+ filename: string; // Original filename
267
+ byte_size: number; // File size in bytes
268
+ md5: string; // MD5 hash of file
269
+ mime_type: string; // MIME type
270
+ width: number | null; // Width in pixels (images/video only)
271
+ height: number | null; // Height in pixels (images/video only)
272
+ }
273
+ ```
274
+
275
+ **Example:**
276
+ ```json
277
+ {
278
+ "topic": "file.ready",
279
+ "data": {
280
+ "id": "660e8400-e29b-41d4-a716-446655440001",
281
+ "type": "video",
282
+ "status": "ready",
283
+ "filename": "my-video.mp4",
284
+ "byte_size": 52428800,
285
+ "md5": "5d41402abc4b2a76b9719d911017c592",
286
+ "mime_type": "video/mp4",
287
+ "width": 1920,
288
+ "height": 1080
289
+ }
290
+ }
291
+ ```
292
+
293
+ **Usage:**
294
+ ```typescript
295
+ if (event.topic === "file.ready") {
296
+ const { id, type, filename } = event.data;
297
+
298
+ console.log(`File ${filename} (${type}) is ready`);
299
+ console.log(`Use in composition: <ef-${type} file-id="${id}"></${type}>`);
300
+
301
+ // Update database record
302
+ await db.updateFile(id, { status: "ready", processedAt: new Date() });
303
+ }
304
+ ```
305
+
306
+ ### file.failed
307
+
308
+ Triggered when file upload or processing fails.
309
+
310
+ Payload structure similar to other file events, with `status: "failed"`.
311
+
312
+ ## Legacy Events
313
+
314
+ ### unprocessed_file.created
315
+
316
+ **Deprecated**: Use `file.created` instead.
317
+
318
+ Triggered when an unprocessed file is created.
319
+
320
+ ```typescript
321
+ interface UnprocessedFileCreatedPayload {
322
+ id: string; // File UUID
323
+ byte_size: number; // File size in bytes
324
+ next_byte: number; // Upload progress
325
+ md5: string | null; // MD5 hash if completed
326
+ filename: string; // Original filename
327
+ completed_at: string | null; // Completion timestamp
328
+ }
329
+ ```
330
+
331
+ ## Webhook Test Event
332
+
333
+ ### webhook.test
334
+
335
+ Sent when you trigger a test webhook from the dashboard.
336
+
337
+ ```json
338
+ {
339
+ "topic": "webhook.test",
340
+ "data": {
341
+ "id": "your-api-key-id",
342
+ "org_id": "your-org-id"
343
+ }
344
+ }
345
+ ```
346
+
347
+ Use this to verify your webhook endpoint is correctly configured.
348
+
349
+ ## Event Ordering
350
+
351
+ Events are sent in chronological order, but network issues or retries may cause events to arrive out of order. Use timestamps (`created_at`, `completed_at`, etc.) to determine actual event sequence.
352
+
353
+ Example render lifecycle:
354
+ 1. `render.created` (status: "created")
355
+ 2. `render.pending` (status: "pending")
356
+ 3. `render.rendering` (status: "rendering")
357
+ 4. `render.completed` (status: "complete") OR `render.failed` (status: "failed")
358
+
359
+ ## Subscribing to Events
360
+
361
+ Configure which events you receive when creating or updating an API key:
362
+
363
+ ```typescript
364
+ const apiKey = await createApiKey({
365
+ name: "Production",
366
+ webhookUrl: "https://api.yourapp.com/webhooks",
367
+ webhookEvents: [
368
+ "render.completed",
369
+ "render.failed",
370
+ "file.ready",
371
+ "file.failed"
372
+ ]
373
+ });
374
+ ```
375
+
376
+ **Recommendation**: Subscribe only to events you need to reduce unnecessary webhook traffic.
377
+
378
+ ## Next Steps
379
+
380
+ - [security.md](references/security.md) — Verify webhook signatures
381
+ - [testing.md](references/testing.md) — Test webhook payloads locally
382
+ - [troubleshooting.md](references/troubleshooting.md) — Debug webhook issues
@@ -0,0 +1,232 @@
1
+ ---
2
+ title: Getting Started with Webhooks
3
+ description: Configure a webhook endpoint URL to receive Editframe event notifications for render completion and file processing.
4
+ type: tutorial
5
+ nav:
6
+ parent: "Quick Start"
7
+ priority: 1
8
+ ---
9
+
10
+ # Getting Started with Webhooks
11
+
12
+ Configure webhooks to receive real-time notifications when renders complete or files finish processing.
13
+
14
+ ## Prerequisites
15
+
16
+ - An Editframe account with API access
17
+ - A publicly accessible HTTPS endpoint to receive webhooks
18
+ - Ability to verify HMAC signatures in your application
19
+
20
+ ## Step 1: Create a Webhook Endpoint
21
+
22
+ Create an endpoint in your application that accepts POST requests:
23
+
24
+ ```typescript
25
+ import express from "express";
26
+ import crypto from "node:crypto";
27
+
28
+ const app = express();
29
+ app.use(express.json());
30
+
31
+ app.post("/webhooks/editframe", async (req, res) => {
32
+ const signature = req.headers["x-webhook-signature"];
33
+ const payload = req.body;
34
+
35
+ // TODO: Verify signature (see Step 3)
36
+
37
+ console.log("Webhook received:", payload.topic);
38
+
39
+ // Respond quickly - process events asynchronously
40
+ res.status(200).send("OK");
41
+
42
+ // Process event in background
43
+ processWebhookEvent(payload).catch(console.error);
44
+ });
45
+
46
+ app.listen(3000);
47
+ ```
48
+
49
+ Your endpoint must:
50
+ - Accept POST requests with JSON body
51
+ - Respond with 200 OK within 30 seconds
52
+ - Verify the `X-Webhook-Signature` header
53
+ - Handle events idempotently (events may be delivered multiple times)
54
+
55
+ ## Step 2: Register Your Webhook
56
+
57
+ Register your webhook URL when creating an API key. You can do this via the Editframe dashboard or programmatically:
58
+
59
+ ### Via Dashboard
60
+
61
+ 1. Go to [editframe.com/resource/api_keys](https://editframe.com/resource/api_keys)
62
+ 2. Click "Create API Key"
63
+ 3. Fill in the form:
64
+ - **Name**: "My Application"
65
+ - **Webhook URL**: `https://your-app.com/webhooks/editframe`
66
+ - **Webhook Events**: Select events to receive:
67
+ - `render.completed`
68
+ - `render.failed`
69
+ - `file.ready`
70
+ 4. Click "Create"
71
+ 5. Copy and securely store:
72
+ - **API Key** (for making API requests)
73
+ - **Webhook Secret** (for verifying signatures)
74
+
75
+ ### Via API (Programmatic)
76
+
77
+ ```typescript
78
+ import { db } from "@/sql-client.server";
79
+ import { createApiKey } from "@/createApiKey.server";
80
+ import { generateApiToken } from "@/util/scryptPromise.server";
81
+ import crypto from "node:crypto";
82
+
83
+ // Generate tokens
84
+ const apiToken = crypto.randomBytes(32).toString("hex");
85
+ const webhookSecret = crypto.randomBytes(32).toString("hex");
86
+
87
+ // Create API key with webhook configuration
88
+ const apiKey = await createApiKey({
89
+ token: apiToken,
90
+ webhookSecret: webhookSecret,
91
+ name: "My Application",
92
+ orgId: "your-org-id",
93
+ userId: "your-user-id",
94
+ webhookUrl: "https://your-app.com/webhooks/editframe",
95
+ webhookEvents: ["render.completed", "render.failed", "file.ready"],
96
+ expired_at: null, // or set expiration date
97
+ });
98
+
99
+ console.log("API Key ID:", apiKey.id);
100
+ console.log("API Token:", apiToken); // Store securely
101
+ console.log("Webhook Secret:", webhookSecret); // Store securely
102
+ ```
103
+
104
+ ## Step 3: Verify Webhook Signatures
105
+
106
+ Every webhook request includes an `X-Webhook-Signature` header containing an HMAC-SHA256 signature. Verify this signature to ensure the request is from Editframe:
107
+
108
+ ```typescript
109
+ import crypto from "node:crypto";
110
+
111
+ function verifyWebhookSignature(
112
+ payload: string,
113
+ signature: string,
114
+ secret: string
115
+ ): boolean {
116
+ const expectedSignature = crypto
117
+ .createHmac("sha256", secret)
118
+ .update(payload)
119
+ .digest("hex");
120
+
121
+ return crypto.timingSafeEqual(
122
+ Buffer.from(signature),
123
+ Buffer.from(expectedSignature)
124
+ );
125
+ }
126
+
127
+ // In your webhook handler
128
+ app.post("/webhooks/editframe", async (req, res) => {
129
+ const signature = req.headers["x-webhook-signature"] as string;
130
+ const payload = JSON.stringify(req.body);
131
+ const secret = process.env.EDITFRAME_WEBHOOK_SECRET!;
132
+
133
+ if (!verifyWebhookSignature(payload, signature, secret)) {
134
+ console.error("Invalid webhook signature");
135
+ return res.status(401).send("Invalid signature");
136
+ }
137
+
138
+ // Signature is valid - process event
139
+ const { topic, data } = req.body;
140
+ console.log(`Verified webhook: ${topic}`);
141
+
142
+ res.status(200).send("OK");
143
+ });
144
+ ```
145
+
146
+ **Critical**: Hash the raw request body as received, not the parsed JSON object. Different JSON serialization can produce different hashes.
147
+
148
+ ## Step 4: Handle Webhook Events
149
+
150
+ Process events based on their topic:
151
+
152
+ ```typescript
153
+ async function processWebhookEvent(event: WebhookEvent) {
154
+ const { topic, data } = event;
155
+
156
+ switch (topic) {
157
+ case "render.completed":
158
+ console.log(`Render ${data.id} completed`);
159
+ console.log(`Download URL: ${data.download_url}`);
160
+ console.log(`Duration: ${data.duration_ms}ms`);
161
+ console.log(`Size: ${data.byte_size} bytes`);
162
+
163
+ // Download the render
164
+ const response = await fetch(data.download_url);
165
+ const videoBuffer = await response.arrayBuffer();
166
+ // ... save or process video
167
+ break;
168
+
169
+ case "render.failed":
170
+ console.error(`Render ${data.id} failed`);
171
+ // ... notify user or retry
172
+ break;
173
+
174
+ case "file.ready":
175
+ console.log(`File ${data.id} is ready`);
176
+ // ... use file in composition
177
+ break;
178
+
179
+ default:
180
+ console.log(`Unhandled webhook topic: ${topic}`);
181
+ }
182
+ }
183
+ ```
184
+
185
+ ## Step 5: Test Your Webhook
186
+
187
+ Use the Editframe dashboard to send a test webhook:
188
+
189
+ 1. Go to your API key detail page
190
+ 2. Click "Test Webhook"
191
+ 3. Select a topic (e.g., "render.completed")
192
+ 4. Click "Send Test"
193
+
194
+ Your endpoint should receive a test webhook with sample data. Check your server logs to verify the webhook was received and the signature was validated.
195
+
196
+ ## Local Development
197
+
198
+ For local testing, use a tunneling service like ngrok:
199
+
200
+ ```bash
201
+ # Start your local server
202
+ npm run dev
203
+
204
+ # In another terminal, start ngrok
205
+ ngrok http 3000
206
+
207
+ # Use the ngrok URL as your webhook URL
208
+ # Example: https://abc123.ngrok.io/webhooks/editframe
209
+ ```
210
+
211
+ Update your API key's webhook URL to the ngrok URL, then trigger events to test locally.
212
+
213
+ See [testing.md](references/testing.md) for more testing strategies.
214
+
215
+ ## Production Considerations
216
+
217
+ When deploying to production:
218
+
219
+ 1. **Use HTTPS**: Webhook URLs must use HTTPS (not HTTP)
220
+ 2. **Store secrets securely**: Never commit webhook secrets to version control
221
+ 3. **Respond quickly**: Return 200 OK within 30 seconds
222
+ 4. **Process asynchronously**: Handle events in background jobs/queues
223
+ 5. **Implement idempotency**: Use event IDs to prevent duplicate processing
224
+ 6. **Log delivery failures**: Monitor webhook delivery in the dashboard
225
+ 7. **Handle retries**: Events may be delivered multiple times
226
+
227
+ ## Next Steps
228
+
229
+ - [events.md](references/events.md) — Learn about all event types and payload structures
230
+ - [security.md](references/security.md) — Deep dive into signature verification
231
+ - [testing.md](references/testing.md) — Advanced testing strategies
232
+ - [troubleshooting.md](references/troubleshooting.md) — Debug common issues