Agent skill
Leonardo.Ai Webhooks Skill
Receive and authenticate Leonardo.Ai webhook callbacks. Use when setting up a Leonardo.Ai webhook callback endpoint, verifying the Authorization Bearer header, debugging 401s, or handling image_generation.complete events.
Install this skill
npx skills add hookdeck/webhook-skills --skill leonardo-ai-webhooks
Leonardo.Ai (the generative image API) can POST a JSON webhook callback when an image generation finishes. Callbacks are not signed. If you set a webhook callback API key, Leonardo.Ai sends it in the Authorization: Bearer <key> header of every callback, and your endpoint authenticates the request by comparing it with the key you stored.
Hookdeck's source type is LEONARDO_AI (labelled "Leonardo.Ai"). Its setup note is: "Set a webhook callback API key on the production API key, then paste it here."
When to Use This Skill
- How do I receive Leonardo.Ai webhook callbacks?
- How do I verify the
Authorization: Bearerheader on a Leonardo.Ai webhook? - Does Leonardo.Ai sign webhook payloads? (No: use the callback API key)
- How do I handle
image_events?generation.complete - Why does my Leonardo.Ai webhook endpoint return 401?
Verification (core)
const crypto = require('crypto');
function verifyLeonardoRequest(headers, configured = process.env.LEONARDO_AI_WEBHOOK_SECRET) {
if (!configured) return false; // never accept when unconfigured
// Store the bare key; tolerate a pasted "Bearer <key>" like Hookdeck does.
const secret = /^bearer /i.test(configured) ? configured.slice(7) : configured;
if (!secret) return false; // a configured value of just "Bearer " must not match
const header = headers['authorization'] || '';
if (!/^bearer /i.test(header)) return false; // scheme is case-insensitive (RFC 9110)
const received = Buffer.from(header.slice(7));
const expected = Buffer.from(secret);
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
- Header:
Authorization: Bearer <webhook callback API key>. Store only the key inLEONARDO_; don't trim or alter it.AI_ WEBHOOK_ SECRET - Missing header, wrong scheme or mismatch: return
401. Invalid JSON:400. Handled:200. - Read the body raw (
express.raw({ type: '*/*' })) and authenticate before parsing it. - A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS only, and rotate the key if it leaks.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
Dispatch on the type field. The payload is {type, object, timestamp (ms), api_.
| Event | Sent When |
|---|---|
image_ | An image generation made with the production API key finishes; data.object is the generation (id, status, images, ...) |
Acknowledge (200) any other type so new event types don't cause retries or alerts.
Environment Variables
LEONARDO_AI_WEBHOOK_SECRET=your-webhook-callback-api-key # the bare key, without "Bearer "
The callback URL and key are optional settings on a production API key. Changing them means creating a new production API key, so update the variable when you do.
Local Development
Leonardo.Ai can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 leonardo-ai --path /webhooks/leonardo-ai
Use the printed URL as the webhook callback URL on the production API key.
Resources
- references/overview.md - What Leonardo.Ai webhooks are, event and payload shape
- references/setup.md - Set the callback URL and API key
- references/verification.md - Bearer check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests