POST /v1/upscale answers 410 gone. Retrieving, listing, downloading and cancelling existing jobs still work. Image upscaling has moved to AI Suite.Webhooks
Register an HTTPS endpoint and we POST to it the moment a job reaches a final state. Every delivery is signed. Verify the signature before trusting the payload.
Setup
Add your endpoint at Dashboard → Webhooks. Each endpoint gets its own signing secret, shown at creation. Deliveries are sent for these events:
| Parameter | Type | Description |
|---|---|---|
| job.succeeded | event | The job finished and the result is downloadable. |
| job.failed | event | The engine gave up; credits were refunded. |
| job.cancelled | event | The job was cancelled before finishing. |
The delivery
Deliveries are JSON POSTs. Respond with any 2xx to acknowledge. Anything else (including a timeout) is retried with backoff.
Delivery payload
{
"id": "evt_8f2ka91m",
"type": "job.succeeded",
"created_at": "2026-07-25T09:30:04Z",
"data": {
"id": "cmrw0iqm8000bdcs3um6ii7fa",
"object": "job",
"status": "succeeded",
"target": "4k",
"credits": 3,
"result": {
"bytes": 8412930,
"content_type": "image/png",
"url": "/api/v1/jobs/cmrw0iqm8000bdcs3um6ii7fa/result"
},
"created_at": "2026-07-25T09:30:00Z",
"finished_at": "2026-07-25T09:30:04Z"
}
}Verify the signature
Each delivery carries an X-Upscalr-Signature header of the form t=<unix>,v1=<hmac>. The HMAC is SHA-256 over <timestamp>.<raw body> using your endpoint secret. Verify against the raw request bytes (parsing and re-serialising the JSON first will change them), and reject deliveries older than five minutes to block replays.
import crypto from "node:crypto";
export function verify(header, rawBody, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
// Reject replays: the timestamp is part of the signed material.
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Good practice
- Acknowledge fast: return 2xx first, then do slow work (like downloading the result) out of band. A handler that takes too long looks like a failure and gets retried.
- Deliveries can arrive more than once, so treat the event
idas an idempotency key on your side. - Don’t rely on ordering. If two events race, fetch the job for its current state instead of trusting the older payload.