Webhooks

Get notified when jobs complete instead of polling. Register a webhook URL and Masko sends a POST request with job results as soon as they are ready.

Create a Webhook

Register a webhook endpoint with the events you want to receive. The response includes a secret for verifying payloads.

POST/v1/webhooks
curl -X POST https://api.masko.ai/v1/webhooks \
  -H "Authorization: Bearer masko_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/api/masko-webhook",
    "events": ["job.completed", "job.failed"]
  }'

# Response:
# {
#   "id": "wh_abc123",
#   "url": "https://your-app.com/api/masko-webhook",
#   "events": ["job.completed", "job.failed"],
#   "secret": "whsec_k7x9m2p4q8r1...",
#   "created_at": "2026-03-28T10:00:00Z"
# }
Warning

Save the secret immediately. It cannot be retrieved later. If you lose it, delete the webhook and create a new one.

Webhook Payload

When a subscribed event fires, Masko sends a POST request to your URL with a JSON body:

{
  "event": "job.completed",
  "job_id": "job_abc123",
  "type": "animation",
  "collection_id": "col_xyz",
  "asset_ids": {
    "image": "ast_img_001",
    "transparent_image": "ast_timg_001",
    "video": "ast_vid_001",
    "webm": "ast_webm_001",
    "hevc": "ast_hevc_001"
  },
  "urls": {
    "image": "https://assets.masko.ai/col_xyz/image.png",
    "transparent_image": "https://assets.masko.ai/col_xyz/transparent.png",
    "video": "https://assets.masko.ai/col_xyz/video.mp4",
    "webm": "https://assets.masko.ai/col_xyz/video.webm",
    "hevc": "https://assets.masko.ai/col_xyz/video_hevc.mp4"
  },
  "timestamp": "2026-03-28T10:01:32Z"
}

Verify Signatures

Every webhook request includes a X-Masko-Signature header containing an HMAC-SHA256 signature of the request body. Verify it using the secret from when you created the webhook.

import crypto from 'crypto';

function verifyWebhook(body, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body, 'utf-8')
    .digest('hex');

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

// In your webhook handler:
app.post('/api/masko-webhook', (req, res) => {
  const signature = req.headers['x-masko-signature'];
  const isValid = verifyWebhook(
    JSON.stringify(req.body),
    signature,
    process.env.MASKO_WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const { event, job_id, urls } = req.body;
  console.log(`Job ${job_id} ${event}:`, urls);

  res.status(200).send('OK');
});

Retry Policy

If your endpoint returns a non-2xx status code or the request times out, Masko retries with exponential backoff:

  • Retry 1: after 1 second
  • Retry 2: after 5 seconds
  • Retry 3: after 30 seconds

After 3 failed attempts, the delivery is marked as failed. If a webhook accumulates 100 consecutive delivery failures, it is automatically disabled. You can re-enable it by deleting and recreating it.

Manage Webhooks

List all your registered webhooks:

GET/v1/webhooks
curl https://api.masko.ai/v1/webhooks \
  -H "Authorization: Bearer masko_YOUR_API_KEY"

# Response:
# {
#   "webhooks": [
#     {
#       "id": "wh_abc123",
#       "url": "https://your-app.com/api/masko-webhook",
#       "events": ["job.completed", "job.failed"],
#       "created_at": "2026-03-28T10:00:00Z"
#     }
#   ]
# }

Delete a webhook when you no longer need it:

DELETE/v1/webhooks/:id
curl -X DELETE https://api.masko.ai/v1/webhooks/wh_abc123 \
  -H "Authorization: Bearer masko_YOUR_API_KEY"

# Response: 204 No Content