API Reference
Webhooks

Webhooks

Receive real-time notifications when events occur in your Canvelete (opens in a new tab) account.

Overview

Webhooks allow your application to receive HTTP POST requests when specific events happen, such as:

  • Render job completed
  • Design created or updated
  • Export finished
  • Subscription changed

Setup

Configure Webhook URL

  1. Go to Settings → Webhooks (opens in a new tab)
  2. Click Add Webhook
  3. Enter your endpoint URL
  4. Select events to subscribe to
  5. Save and copy the signing secret

Webhook Endpoint Requirements

Your endpoint must:

  • Accept POST requests
  • Return 2xx status within 30 seconds
  • Handle duplicate deliveries (use eventId for deduplication)

Event Types

EventDescription
render.completedRender job finished successfully
render.failedRender job failed
design.createdNew design created
design.updatedDesign modified
design.deletedDesign removed
export.completedExport finished
subscription.updatedPlan changed

Payload Format

{
  "eventId": "evt_abc123",
  "eventType": "render.completed",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "renderId": "render_xyz789",
    "designId": "design_abc123",
    "status": "completed",
    "downloadUrl": "https://cdn.canvelete.com/renders/xyz789.png",
    "metadata": {
      "format": "png",
      "width": 1080,
      "height": 1080,
      "fileSize": 245760
    }
  }
}

Signature Verification

All webhooks include a signature header for verification:

X-Canvelete-Signature: sha256=abc123...

Verify in Node.js

const crypto = require('crypto');
 
function verifyWebhook(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return `sha256=${expectedSignature}` === signature;
}
 
// Express middleware
app.post('/webhooks/canvelete', (req, res) => {
  const signature = req.headers['x-canvelete-signature'];
  const payload = JSON.stringify(req.body);
  
  if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process webhook
  const { eventType, data } = req.body;
  console.log(`Received ${eventType}:`, data);
  
  res.status(200).send('OK');
});

Verify in Python

import hmac
import hashlib
 
def verify_webhook(payload: str, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()
    return f"sha256={expected}" == signature
 
# Flask example
@app.route('/webhooks/canvelete', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Canvelete-Signature')
    payload = request.get_data(as_text=True)
    
    if not verify_webhook(payload, signature, WEBHOOK_SECRET):
        return 'Invalid signature', 401
    
    data = request.json
    print(f"Received {data['eventType']}: {data['data']}")
    
    return 'OK', 200

Event Examples

render.completed

{
  "eventId": "evt_abc123",
  "eventType": "render.completed",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "renderId": "render_xyz789",
    "designId": "design_abc123",
    "status": "completed",
    "downloadUrl": "https://cdn.canvelete.com/renders/xyz789.png",
    "metadata": {
      "format": "png",
      "width": 1080,
      "height": 1080,
      "fileSize": 245760,
      "renderTime": 1.2
    }
  }
}

render.failed

{
  "eventId": "evt_def456",
  "eventType": "render.failed",
  "timestamp": "2024-01-15T10:31:00Z",
  "data": {
    "renderId": "render_failed123",
    "designId": "design_abc123",
    "status": "failed",
    "error": {
      "code": "RENDER_TIMEOUT",
      "message": "Render exceeded maximum time limit"
    }
  }
}

design.created

{
  "eventId": "evt_ghi789",
  "eventType": "design.created",
  "timestamp": "2024-01-15T10:32:00Z",
  "data": {
    "designId": "design_new123",
    "name": "New Design",
    "width": 1920,
    "height": 1080,
    "createdBy": "user_abc123"
  }
}

Retry Policy

Failed webhook deliveries are retried with exponential backoff:

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours

After 5 failed attempts, the webhook is marked as failed and no further retries occur.

Best Practices

  1. Respond quickly — Return 200 immediately, process asynchronously
  2. Handle duplicates — Use eventId for idempotency
  3. Verify signatures — Always validate the webhook signature
  4. Log events — Keep records for debugging
  5. Use HTTPS — Secure your webhook endpoint

Testing Webhooks

Test Endpoint

Send a test webhook from your dashboard (opens in a new tab):

  1. Go to Settings → Webhooks
  2. Click Test next to your webhook
  3. Select an event type
  4. Click Send Test

Local Development

Use tools like ngrok (opens in a new tab) to expose local endpoints:

ngrok http 3000

Then use the ngrok URL as your webhook endpoint.

SDK Helpers

Python

from canvelete import CanveleteClient
 
client = CanveleteClient(api_key="YOUR_API_KEY")
 
# Verify webhook
is_valid = client.webhooks.verify(
    payload=request_body,
    signature=signature_header,
    secret=webhook_secret
)

TypeScript

import { CanveleteClient } from '@canveletedotcom/sdk';
 
const client = new CanveleteClient({ apiKey: 'YOUR_API_KEY' });
 
// Verify webhook
const isValid = client.webhooks.verify({
  payload: requestBody,
  signature: signatureHeader,
  secret: webhookSecret
});

Next Steps