Skip to main content

Webhooks

Webhooks allow you to receive HTTP callbacks when your jobs complete or fail, eliminating the need for polling.

Overview

Instead of repeatedly polling GET /v1/jobs/{jobId} for status updates, you can provide a webhookUrl when submitting a job. PixelByte will send an HTTP POST to your URL when the job reaches a terminal state.
Your App → POST /v1/jobs/submit (with webhookUrl) → PixelByte

Your App ← POST webhookUrl (job result) ←──────────────┘

Setting Up Webhooks

Include a webhookUrl in your job submission request:
curl -X POST https://api.muvi.video/v1/jobs/submit \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "modelSlug": "stability/sdxl",
    "modelVersion": "v1",
    "input": {
      "prompt": "A serene mountain landscape"
    },
    "webhookUrl": "https://your-app.com/webhooks/pixelbyte"
  }'

Webhook Payload

When a job completes or fails, PixelByte sends a POST request to your webhook URL with the following payload:

Success Payload

{
  "jobId": "job_abc123",
  "requestId": "req_xyz789",
  "status": "completed",
  "output": {
    "imageUrl": "https://cdn.muvi.video/outputs/job_abc123.png",
    "metadata": {
      "width": 1024,
      "height": 1024,
      "format": "png"
    }
  },
  "completedAt": "2026-02-18T12:00:00.000Z"
}

Failure Payload

{
  "jobId": "job_abc123",
  "requestId": "req_xyz789",
  "status": "failed",
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "Model inference failed"
  },
  "completedAt": "2026-02-18T12:00:05.000Z"
}
FieldTypeDescription
jobIdstringThe unique job identifier
requestIdstringThe original request ID
statusstringcompleted or failed
outputobjectJob output (only on success)
errorobjectError details (only on failure)
completedAtstringISO 8601 timestamp

Signature Verification

Every webhook request is signed so you can verify it came from PixelByte.

Signature Headers

HeaderDescription
X-PixelByte-TimestampUnix timestamp (seconds) when the webhook was sent
X-PixelByte-SignatureHMAC-SHA256 signature in format sha256=...

How Verification Works

The signature is computed as:
HMAC-SHA256(timestamp + "." + rawPayload, webhookSecret)
Where:
  • timestamp is the value from the X-PixelByte-Timestamp header
  • rawPayload is the raw request body string
  • webhookSecret is your webhook secret from the dashboard

Verification Examples

const crypto = require("crypto");

function verifyWebhookSignature(req, webhookSecret) {
  const timestamp = req.headers["x-pixelbyte-timestamp"];
  const signature = req.headers["x-pixelbyte-signature"];

  if (!timestamp || !signature) {
    return false;
  }

  // Reject requests older than 5 minutes
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
  if (age > 300) {
    return false;
  }

  const rawBody = JSON.stringify(req.body);
  const signedPayload = `${timestamp}.${rawBody}`;

  const expectedSignature = "sha256=" + crypto
    .createHmac("sha256", webhookSecret)
    .update(signedPayload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express.js example
app.post("/webhooks/pixelbyte", express.json(), (req, res) => {
  const isValid = verifyWebhookSignature(req, process.env.WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  // Respond 200 immediately
  res.status(200).json({ received: true });

  // Process asynchronously
  processWebhook(req.body).catch(console.error);
});
Always use constant-time comparison (timingSafeEqual in Node.js, hmac.compare_digest in Python) to prevent timing attacks.

Retry Policy

If your endpoint doesn’t respond with a 2xx status code, PixelByte retries the webhook delivery using exponential backoff:
AttemptDelayCumulative Time
1st retry1 second1s
2nd retry4 seconds5s
3rd retry16 seconds21s
4th retry64 seconds~1.4 min
5th retry256 seconds~5.7 min
After 5 failed attempts, the webhook is marked as failed.

Webhook Delivery Status

StatusDescription
pendingWebhook is queued for delivery
sentSuccessfully delivered (received 2xx response)
failedAll retry attempts exhausted

Best Practices

1

Respond 200 Quickly

Return a 200 response immediately before doing any processing. Heavy processing should happen asynchronously to avoid timeouts.
2

Verify Signatures

Always verify the X-PixelByte-Signature header to ensure the webhook is authentic. Never skip this step, even in development.
3

Handle Idempotency

Webhooks may be delivered more than once (due to retries). Use the jobId to deduplicate and ensure your handler is idempotent.
// Store processed job IDs
if (await isAlreadyProcessed(payload.jobId)) {
  return res.status(200).json({ received: true });
}
await markAsProcessed(payload.jobId);
4

Process Asynchronously

Queue webhook payloads for background processing. This ensures you respond quickly and can retry your own processing if needed.
5

Use HTTPS

Always use HTTPS URLs for webhook endpoints. HTTP URLs will be rejected.
6

Monitor Delivery

Track webhook delivery status in the PixelByte dashboard and set up alerts for repeated failures.
Webhook URLs must be publicly accessible and respond within 30 seconds. If your endpoint consistently times out, webhooks will be marked as failed.