@editframe/create 0.44.0 → 0.45.1
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.
- package/dist/index.js +16 -28
- package/dist/index.js.map +1 -1
- package/dist/skills/editframe-brand-video-generator/README.md +155 -0
- package/dist/skills/editframe-brand-video-generator/SKILL.md +207 -0
- package/dist/skills/editframe-brand-video-generator/references/brand-examples.md +178 -0
- package/dist/skills/editframe-brand-video-generator/references/color-psychology.md +227 -0
- package/dist/skills/editframe-brand-video-generator/references/composition-patterns.md +383 -0
- package/dist/skills/editframe-brand-video-generator/references/editing.md +66 -0
- package/dist/skills/editframe-brand-video-generator/references/emotional-arcs.md +496 -0
- package/dist/skills/editframe-brand-video-generator/references/genre-selection.md +135 -0
- package/dist/skills/editframe-brand-video-generator/references/transition-styles.md +611 -0
- package/dist/skills/editframe-brand-video-generator/references/typography-personalities.md +326 -0
- package/dist/skills/editframe-brand-video-generator/references/video-archetypes.md +86 -0
- package/dist/skills/editframe-brand-video-generator/references/video-fundamentals.md +169 -0
- package/dist/skills/editframe-brand-video-generator/references/visual-metaphors.md +50 -0
- package/dist/skills/editframe-composition/SKILL.md +169 -0
- package/dist/skills/editframe-composition/references/audio.md +483 -0
- package/dist/skills/editframe-composition/references/captions.md +844 -0
- package/dist/skills/editframe-composition/references/composition-model.md +73 -0
- package/dist/skills/editframe-composition/references/configuration.md +403 -0
- package/dist/skills/editframe-composition/references/css-parts.md +105 -0
- package/dist/skills/editframe-composition/references/css-variables.md +640 -0
- package/dist/skills/editframe-composition/references/entry-points.md +810 -0
- package/dist/skills/editframe-composition/references/events.md +499 -0
- package/dist/skills/editframe-composition/references/getting-started.md +259 -0
- package/dist/skills/editframe-composition/references/hooks.md +234 -0
- package/dist/skills/editframe-composition/references/image.md +241 -0
- package/dist/skills/editframe-composition/references/r3f.md +580 -0
- package/dist/skills/editframe-composition/references/render-api.md +484 -0
- package/dist/skills/editframe-composition/references/render-strategies.md +119 -0
- package/dist/skills/editframe-composition/references/render-to-video.md +1101 -0
- package/dist/skills/editframe-composition/references/scripting.md +606 -0
- package/dist/skills/editframe-composition/references/sequencing.md +116 -0
- package/dist/skills/editframe-composition/references/server-rendering.md +753 -0
- package/dist/skills/editframe-composition/references/surface.md +329 -0
- package/dist/skills/editframe-composition/references/text.md +627 -0
- package/dist/skills/editframe-composition/references/time-model.md +99 -0
- package/dist/skills/editframe-composition/references/timegroup-modes.md +102 -0
- package/dist/skills/editframe-composition/references/timegroup.md +457 -0
- package/dist/skills/editframe-composition/references/timeline-root.md +398 -0
- package/dist/skills/editframe-composition/references/transcription.md +47 -0
- package/dist/skills/editframe-composition/references/transitions.md +608 -0
- package/dist/skills/editframe-composition/references/use-media-info.md +357 -0
- package/dist/skills/editframe-composition/references/video.md +506 -0
- package/dist/skills/editframe-composition/references/waveform.md +327 -0
- package/dist/skills/editframe-editor-gui/SKILL.md +152 -0
- package/dist/skills/editframe-editor-gui/references/active-root-temporal.md +657 -0
- package/dist/skills/editframe-editor-gui/references/canvas.md +947 -0
- package/dist/skills/editframe-editor-gui/references/controls.md +366 -0
- package/dist/skills/editframe-editor-gui/references/dial.md +756 -0
- package/dist/skills/editframe-editor-gui/references/editor-toolkit.md +587 -0
- package/dist/skills/editframe-editor-gui/references/filmstrip.md +460 -0
- package/dist/skills/editframe-editor-gui/references/fit-scale.md +772 -0
- package/dist/skills/editframe-editor-gui/references/focus-overlay.md +561 -0
- package/dist/skills/editframe-editor-gui/references/hierarchy.md +544 -0
- package/dist/skills/editframe-editor-gui/references/overlay-item.md +634 -0
- package/dist/skills/editframe-editor-gui/references/overlay-layer.md +429 -0
- package/dist/skills/editframe-editor-gui/references/pan-zoom.md +568 -0
- package/dist/skills/editframe-editor-gui/references/pause.md +397 -0
- package/dist/skills/editframe-editor-gui/references/play.md +370 -0
- package/dist/skills/editframe-editor-gui/references/preview.md +391 -0
- package/dist/skills/editframe-editor-gui/references/resizable-box.md +749 -0
- package/dist/skills/editframe-editor-gui/references/scrubber.md +588 -0
- package/dist/skills/editframe-editor-gui/references/thumbnail-strip.md +566 -0
- package/dist/skills/editframe-editor-gui/references/time-display.md +492 -0
- package/dist/skills/editframe-editor-gui/references/timeline-ruler.md +489 -0
- package/dist/skills/editframe-editor-gui/references/timeline.md +604 -0
- package/dist/skills/editframe-editor-gui/references/toggle-loop.md +618 -0
- package/dist/skills/editframe-editor-gui/references/toggle-play.md +526 -0
- package/dist/skills/editframe-editor-gui/references/transform-handles.md +924 -0
- package/dist/skills/editframe-editor-gui/references/trim-handles.md +725 -0
- package/dist/skills/editframe-editor-gui/references/workbench.md +453 -0
- package/dist/skills/editframe-motion-design/SKILL.md +101 -0
- package/dist/skills/editframe-motion-design/references/0-editframe.md +299 -0
- package/dist/skills/editframe-motion-design/references/1-intent.md +201 -0
- package/dist/skills/editframe-motion-design/references/2-physics-model.md +405 -0
- package/dist/skills/editframe-motion-design/references/3-attention.md +350 -0
- package/dist/skills/editframe-motion-design/references/4-process.md +418 -0
- package/dist/skills/editframe-vite-plugin/SKILL.md +75 -0
- package/dist/skills/editframe-vite-plugin/references/file-api.md +111 -0
- package/dist/skills/editframe-vite-plugin/references/getting-started.md +96 -0
- package/dist/skills/editframe-vite-plugin/references/jit-transcoding.md +91 -0
- package/dist/skills/editframe-vite-plugin/references/local-assets.md +75 -0
- package/dist/skills/editframe-vite-plugin/references/visual-testing.md +136 -0
- package/dist/skills/editframe-webhooks/SKILL.md +126 -0
- package/dist/skills/editframe-webhooks/references/events.md +382 -0
- package/dist/skills/editframe-webhooks/references/getting-started.md +232 -0
- package/dist/skills/editframe-webhooks/references/security.md +418 -0
- package/dist/skills/editframe-webhooks/references/testing.md +409 -0
- package/dist/skills/editframe-webhooks/references/troubleshooting.md +457 -0
- package/dist/templates/html/AGENTS.md +13 -0
- package/dist/templates/react/AGENTS.md +13 -0
- package/dist/utils.js +15 -16
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
- package/tsdown.config.ts +4 -0
- package/dist/detectAgent.js +0 -89
- 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
|