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 2xx status within a few seconds. Do heavy processing asynchronously
  • Be idempotent — Use the job.id to deduplicate. While unlikely, a webhook may be delivered more than once
  • Download promptly — The outputUrl expires 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.