Webhooks
Instead of polling GET /api/v1/jobs/:id, you can provide a webhook URL when creating a job. EditClips will send an HTTP POST to your URL when the job completes or fails.
Setting up a webhook
Pass a webhook field when creating a job (Pro and Business plans):
curl -X POST https://editclips.online/api/v1/jobs \
-H "Authorization: Bearer ec_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool": "convert-video",
"inputs": ["https://example.com/video.mov"],
"webhook": "https://yourapp.com/webhooks/editclips"
}'
The webhook URL must use HTTPS. HTTP URLs are rejected with a 400 error.
Webhook events
EditClips sends two event types:
job.completed
Sent when a job finishes successfully.
POST https://yourapp.com/webhooks/editclips
Content-Type: application/json
{
"event": "job.completed",
"job": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"tool": "convert-video",
"status": "completed",
"outputUrl": "https://r2.editclips.online/...",
"outputFilename": "video.mp4",
"outputSize": 10485760,
"creditsCharged": 0,
"metrics": {
"wallTimeMs": 83000,
"cpuTimeMs": 72000,
"peakMemoryBytes": 536870912,
"estimatedCostMicroUsd": 1250
}
}
}
The outputUrl is a presigned R2 download link valid for 1 hour. Download the file promptly.
The link is only sent once the result is unlocked. Non-AI results are included in your plan; AI results are charged their credits when they're ready. If your balance can't cover an AI result, outputUrl is null and download says what's needed:
POST https://yourapp.com/webhooks/editclips
Content-Type: application/json
{
"event": "job.completed",
"job": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"tool": "video-remove-background",
"status": "completed",
"outputUrl": null,
"outputFilename": "video.webm",
"outputSize": 10485760,
"creditsCharged": 0,
"download": {
"reason": "insufficient_credits",
"required": 25,
"balance": 12
},
"message": "This result costs 25 AI credits and the balance is 12. Top up at https://editclips.online/pricing, then GET /api/v1/jobs/d290f1ee-6c54-4b01-90e6-d701748f0851 to download it.",
"metrics": null
}
}
Top up, then GET /api/v1/jobs/:id to download the result.
job.failed
Sent when a job permanently fails (after all retry attempts are exhausted).
POST https://yourapp.com/webhooks/editclips
Content-Type: application/json
{
"event": "job.failed",
"job": {
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"tool": "convert-video",
"status": "failed",
"errorMessage": "FFmpeg exited with code 1: Invalid input format"
}
}
A failed job costs nothing.
Webhook fields
| Field | Type | Description |
|---|---|---|
event | string | job.completed or job.failed |
job.id | string | Job UUID |
job.tool | string | Tool slug used for processing |
job.status | string | completed or failed |
job.outputUrl | string | null | Presigned download URL (completed only, valid 1 hour); null while the result is locked |
job.outputFilename | string | Suggested filename (completed only) |
job.outputSize | number | File size in bytes (completed only) |
job.creditsCharged | number | AI credits charged for the result (completed only; 0 for non-AI tools) |
job.download | object | Only while the result is locked: reason (insufficient_credits or plan_required), required (credits, or null when a plan is needed) and balance |
job.message | string | Only while the result is locked: what to do to download it |
job.errorMessage | string | Error description (failed only) |
job.metrics | object | Processing metrics (completed only, may be null) |
Handling webhooks
Best practices
- Respond quickly — Return a
2xxstatus within a few seconds. Do heavy processing asynchronously - Be idempotent — Use the
job.idto deduplicate. While unlikely, a webhook may be delivered more than once - Download promptly — The
outputUrlexpires after 1 hour. If you miss it, poll GET /api/v1/jobs/:id to get a fresh URL - Use polling as a fallback — If your webhook endpoint is unreachable, delivery silently fails. Poll periodically as a safety net
Example handler (Node.js)
app.post("/webhooks/editclips", async (req, res) => {
const { event, job } = req.body
if (event === "job.completed" && job.outputUrl) {
// Download the output file
const response = await fetch(job.outputUrl)
const buffer = await response.arrayBuffer()
await fs.writeFile(`outputs/${job.outputFilename}`, Buffer.from(buffer))
} else if (event === "job.completed") {
// Locked: top up, then GET /api/v1/jobs/:id to download it
console.warn(job.message)
}
if (event === "job.failed") {
console.error(`Job ${job.id} failed: ${job.errorMessage}`)
}
res.sendStatus(200)
})
Retry behavior
Webhook delivery is attempted once. If your endpoint is unreachable or returns an error, the webhook is not retried. The job result remains available via GET /api/v1/jobs/:id — use polling as a fallback to ensure you never miss a result.