Receive HTTP notifications when your jobs complete or fail. No polling required.
| Event | Description |
|---|---|
job.completed | Job finished successfully |
job.failed | Job failed to process |
job.cancelled | Job was cancelled |
{
"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"
}
}
}
{
"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"
}
}
Every webhook request includes these headers:
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC SHA256 signature (sha256=...) |
X-Webhook-Timestamp | Unix timestamp when sent |
X-Webhook-Event | Event type (job.completed, etc.) |
User-Agent | SMAvatar-Webhooks/1.0 |
Always verify webhook signatures to ensure requests are from SMAvatar.
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');
});
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
Send a test webhook from your project settings to verify your endpoint is working:
webhook.test eventTest 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
}
}
Do:
Don't:
Not receiving webhooks?
Invalid signature errors?
{timestamp}.{payload} (not just payload)