Webhooks
Webhooks allow you to receive real-time HTTP notifications when events occur in your Pictify account. Use them to trigger workflows, update databases, or integrate with third-party services.
Supported Events
| Event | Description |
|---|
render.completed | Image, GIF, or PDF render finished successfully |
render.failed | Render failed with an error |
binding.updated | Binding data refreshed |
binding.failed | Binding data fetch failed |
Creating a Webhook
Dashboard
- Go to Settings > Webhooks
- Click Create Webhook
- Select the event type
- Enter your endpoint URL
- Save and copy the signing secret
API
curl -X POST https://api.pictify.io/webhook-subscriptions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "render.completed",
"targetUrl": "https://your-server.com/webhooks/pictify",
"platform": "custom"
}'
Response includes the signing secret (only shown once):
{
"subscription": {
"uid": "wh_abc123",
"event": "render.completed",
"targetUrl": "https://your-server.com/webhooks/pictify",
"status": "active",
"secret": "whsec_xyz789..."
}
}
Webhook Payload
All webhooks include these headers:
Content-Type: application/json
X-Pictify-Signature: t=1706515260,v1=abc123...
X-Pictify-Event: render.completed
X-Pictify-Delivery-Id: del_xyz789
render.completed
{
"event": "render.completed",
"timestamp": "2026-01-29T10:30:00Z",
"data": {
"type": "image",
"source": "api",
"imageId": "img_abc123",
"url": "https://cdn.pictify.io/renders/abc123.png",
"userStorageUrl": "https://your-bucket.s3.amazonaws.com/abc123.png",
"width": 1200,
"height": 630,
"templateId": "tmpl_xyz789",
"userId": "user_123",
"renderedAt": "2026-01-29T10:30:00Z"
}
}
render.failed
{
"event": "render.failed",
"timestamp": "2026-01-29T10:30:00Z",
"data": {
"type": "image",
"templateId": "tmpl_xyz789",
"error": "Template not found",
"errorCode": "TEMPLATE_NOT_FOUND"
}
}
Signature Verification
Always verify webhook signatures to ensure requests are from Pictify and haven’t been tampered with.
The signature header format is:
X-Pictify-Signature: t=1706515260,v1=abc123...
Where:
t = Unix timestamp when the webhook was sent
v1 = HMAC-SHA256 signature of {timestamp}.{payload}
Verification Steps
- Extract timestamp and signature from header
- Reject if timestamp is older than 5 minutes (replay protection)
- Compute expected signature:
HMAC-SHA256(secret, "{timestamp}.{payload}")
- Compare signatures using constant-time comparison
Code Examples
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signatureHeader: string,
secret: string
): boolean {
const parts: Record<string, string> = {};
for (const pair of signatureHeader.split(',')) {
const [key, value] = pair.split('=');
parts[key] = value;
}
const timestamp = parseInt(parts.t, 10);
const providedSignature = parts.v1;
// Reject if timestamp is older than 5 minutes
if (Math.abs(Date.now() / 1000 - timestamp) > 300) {
throw new Error('Webhook timestamp too old');
}
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(providedSignature),
Buffer.from(expectedSignature)
);
}
// Express.js example
app.post('/webhooks/pictify', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-pictify-signature'];
const payload = req.body.toString();
try {
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
console.log('Received event:', event.event);
res.status(200).send('OK');
} catch (error) {
res.status(400).send(error.message);
}
});
Managing Webhooks
List Webhooks
curl https://api.pictify.io/webhook-subscriptions \
-H "Authorization: Bearer $API_KEY"
Pause / Resume a Webhook
Pause and resume are currently available via the dashboard only. API support is coming soon.
Delete a Webhook
curl -X DELETE https://api.pictify.io/webhook-subscriptions/{uid} \
-H "Authorization: Bearer $API_KEY"
Filters
Filter webhooks to receive only specific events:
curl -X POST https://api.pictify.io/webhook-subscriptions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "render.completed",
"targetUrl": "https://your-server.com/webhooks",
"filters": {
"templateId": "tmpl_abc123"
}
}'
Delivery & Retries
- Webhooks are delivered within seconds of events
- Failed deliveries retry with exponential backoff: 1min, 5min, 30min, 2hr, 24hr
- After 5 failed attempts, the webhook is paused
- Check delivery status in the dashboard or via API
Best Practices
- Always verify signatures - Protect against spoofed requests
- Respond quickly - Return 2xx within 30 seconds, process async
- Handle duplicates - Use delivery ID for idempotency
- Monitor failures - Set up alerts for webhook delivery issues
- Use HTTPS - Never use HTTP endpoints in production