Agent skill
Prefect Webhooks Skill
Receive and authenticate Prefect webhooks. Use when setting up an endpoint for Prefect automation notifications sent by the Custom Webhook notification block, checking the secret header you configured, debugging 401s, or handling flow run Failed or Completed notifications. Prefect requests are unsigned.
Install this skill
npx skills add hookdeck/webhook-skills --skill prefect-webhooks
Prefect automations can notify an HTTP endpoint through the Custom Webhook notification block (this skill is about that outbound notification, not Prefect Cloud's inbound webhooks). The request is not signed. You authenticate it with a secret header that you add in the block's headers; your endpoint compares that header with the value it has stored.
A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS and rotate the credential if it leaks.
When to Use This Skill
- How do I receive Prefect webhooks from an automation?
- How do I authenticate Prefect Custom Webhook notifications?
- Does Prefect sign webhook payloads? (No: use a secret header)
- How do I handle a flow run Failed or Completed notification?
- Why does my Prefect webhook endpoint return 401?
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyPrefectRequest(
headers,
secret = process.env.PREFECT_WEBHOOK_SECRET,
headerName = process.env.PREFECT_WEBHOOK_HEADER || 'Authorization'
) {
if (!secret) return false; // never accept when unconfigured
const received = headers[headerName.toLowerCase()];
return Boolean(received) && safeEqual(received, secret); // WHOLE value, no Bearer parsing
}
- The header name is whatever you typed in the block's headers, so read it from
PREFECT_WEBHOOK_HEADER(defaultAuthorization) and look it up case-insensitively. PREFECT_WEBHOOK_SECRETis the exact, whole header value, including anyBearerprefix you typed. Do not strip or parse the prefix.- Python:
hmac.compare_digeston bytes (encode both sides;strraises on non-ASCII). - Missing header or mismatch:
401. Authenticate before parsing the body. - The block's method is configurable (POST by default): accept POST, PUT, PATCH and GET, and handle an empty body. In Express, read the body with
express.raw({ type: '*/*' }).
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Payload and Events
Prefect adds no event field and no event type. The body is the block's json_data or form_data template with {{subject}}, {{body}} and {{name}} substituted (plus any {{secret}} values from the block's secrets). The examples assume this illustrative template; use your own:
{"subject": "{{subject}}", "body": "{{body}}", "name": "{{name}}"}
| Notification (illustrative) | Source |
|---|---|
| flow run Failed notification | An automation fires on a flow run entering Failed |
| flow run Completed notification | An automation fires on a flow run entering Completed |
Which notification you got depends on the automation that triggered it; add your own field to the template (or match on subject) to tell them apart.
Environment Variables
PREFECT_WEBHOOK_HEADER=Authorization # header name you set in the block
PREFECT_WEBHOOK_SECRET=Bearer your-shared-secret # exact whole header value
Local Development
Prefect Cloud can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 prefect --path /webhooks/prefect
Use the printed URL as the Custom Webhook block's URL.
Resources
- references/overview.md - What the Custom Webhook block sends and its payload
- references/setup.md - Create the block, add the secret header, attach it to an automation
- references/verification.md - Header check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests