Webhooks

Receive notifications when jobs complete.

Webhooks

Receive HTTP notifications when your jobs complete or fail. No polling required.

Events

EventDescription
job.completedJob finished successfully
job.failedJob failed to process
job.cancelledJob was cancelled

Configure Webhooks

  1. Open your project in the dashboard
  2. Go to SettingsWebhooks
  3. Enter your endpoint URL (must be HTTPS in production)
  4. Click Generate Secret to create a signing key
  5. Enable webhooks and select which events to receive

Payload Format

{
  "event": "job.completed",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    "job_id": "abc123-def456-ghi789",
    "job_type": "avatar",
    "project_id": "your-project-id",
    "status": "completed",
    "result_url": "https://..../avatars/project-id/job-id/avatar.png",
    "download_url": "https://..../avatars/project-id/job-id/avatar.png",
    "created_at": "2024-01-15T10:29:50.000Z",
    "started_at": "2024-01-15T10:29:51.000Z",
    "completed_at": "2024-01-15T10:30:00.000Z",
    "metadata": {
      "style_id": "cartoon_flat_modern",
      "quality": "standard",
      "size": "square"
    }
  }
}

Failed Job Payload

{
  "event": "job.failed",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    "job_id": "abc123-def456-ghi789",
    "job_type": "avatar",
    "project_id": "your-project-id",
    "status": "failed",
    "error": {
      "code": "GENERATION_FAILED",
      "message": "Image generation failed after 3 attempts"
    },
    "created_at": "2024-01-15T10:29:50.000Z"
  }
}

Headers

Every webhook request includes these headers:

HeaderDescription
X-Webhook-SignatureHMAC SHA256 signature (sha256=...)
X-Webhook-TimestampUnix timestamp when sent
X-Webhook-EventEvent type (job.completed, etc.)
User-AgentSMAvatar-Webhooks/1.0

Verify Signatures

Always verify webhook signatures to ensure requests are from SMAvatar.

Node.js

const crypto = require('crypto');

function verifyWebhook(payload, signature, timestamp, secret) {
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  
  return signature === `sha256=${expectedSignature}`;
}

// Express example
app.post('/webhooks/smavatar', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];
  const payload = JSON.stringify(req.body);
  
  if (!verifyWebhook(payload, signature, timestamp, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  const event = req.body;
  
  if (event.event === 'job.completed') {
    console.log('Job completed:', event.data.job_id);
    // Download image from event.data.download_url
  }
  
  res.status(200).send('OK');
});

Python

import hmac
import hashlib

def verify_webhook(payload: str, signature: str, timestamp: str, secret: str) -> bool:
    signed_payload = f"{timestamp}.{payload}"
    expected = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()
    return signature == f"sha256={expected}"

# Flask example
@app.route('/webhooks/smavatar', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature')
    timestamp = request.headers.get('X-Webhook-Timestamp')
    payload = request.get_data(as_text=True)
    
    if not verify_webhook(payload, signature, timestamp, WEBHOOK_SECRET):
        return 'Invalid signature', 401
    
    event = request.json
    
    if event['event'] == 'job.completed':
        print(f"Job completed: {event['data']['job_id']}")
        # Download image from event['data']['download_url']
    
    return 'OK', 200

Test Webhooks

Send a test webhook from your project settings to verify your endpoint is working:

  1. Go to SettingsWebhooks
  2. Click Send Test Webhook
  3. Check your endpoint received the webhook.test event

Test payload:

{
  "event": "webhook.test",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    "project_id": "your-project-id",
    "project_name": "My Project",
    "message": "This is a test webhook from SMAvatar",
    "test": true
  }
}

Delivery & Retries

  • Timeout: 10 seconds per request
  • Success: HTTP 2xx response
  • Retries: Up to 3 attempts for failed deliveries
  • Logging: All deliveries are logged in your project dashboard

Best Practices

Do:

  • Verify signatures on every request
  • Respond quickly (under 5 seconds)
  • Return 2xx status to acknowledge receipt
  • Process asynchronously for long operations

Don't:

  • Trust requests without signature verification
  • Block on slow operations before responding
  • Expose your webhook secret

Troubleshooting

Not receiving webhooks?

  1. Check webhooks are enabled in project settings
  2. Verify URL is correct and publicly accessible
  3. Check the selected events include the ones you need
  4. Review delivery logs in your project dashboard

Invalid signature errors?

  1. Ensure you're using the correct webhook secret
  2. Verify you're signing {timestamp}.{payload} (not just payload)
  3. Check payload is the raw request body, not parsed JSON

Next Steps

Create Avatar

Generate avatars and receive completion webhooks.

Create Map

Generate maps and receive completion webhooks.