Agent skill
Estuary Webhooks Skill
Receive and authenticate Estuary webhooks. Use when setting up an endpoint for Estuary materialization triggers or the HTTP Webhook materialization connector, checking the secret header you configure (Estuary does not sign requests), debugging 401s, or handling POST, PUT or PATCH deliveries.
Install this skill
npx skills add hookdeck/webhook-skills --skill estuary-webhooks
Estuary (real-time data integration) calls your endpoint from materialization triggers (one request per committed transaction) and from the HTTP Webhook materialization connector (which sends documents). Requests are not signed: there is no signature header, no HMAC and no timestamp. You authenticate by adding a secret header yourself.
Hookdeck's Estuary source setup note says the same thing:
Estuary triggers are not signed. Add a secret header to the materialization trigger (or the HTTP Webhook connector) and enter it here.
When to Use This Skill
- How do I receive Estuary materialization trigger webhooks?
- How do I authenticate requests from the Estuary HTTP Webhook connector?
- Does Estuary sign webhooks? (No: a header you configure, such as
Authorization: Bearer ...) - Why does my Estuary webhook endpoint return 401?
- Which HTTP methods does an Estuary trigger use? (POST by default, or PUT or PATCH)
Verification (core)
A trigger's headers are "HTTP headers to include in the request" and are encrypted at rest; the HTTP Webhook connector takes /headers/customHeaders. The header name is yours to choose, so read it from ESTUARY_WEBHOOK_HEADER (default Authorization) and compare the whole value with ESTUARY_WEBHOOK_SECRET. Do not strip or parse a Bearer prefix: if you typed Bearer my-secret-token as the header value, that exact string is the secret.
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
const digest = (buf) => crypto.createHash('sha256').update(buf).digest();
function verifyEstuaryRequest(headers, env = process.env) {
const expected = env.ESTUARY_WEBHOOK_SECRET;
if (!expected) return false; // never accept when unconfigured
const name = (env.ESTUARY_WEBHOOK_HEADER || 'Authorization').toLowerCase();
const received = headers[name]; // Node lowercases header names
if (!received) return false;
return crypto.timingSafeEqual(
digest(Buffer.from(received, 'latin1')), digest(Buffer.from(expected, 'utf8')));
}
Return 401 on a missing header or mismatch, for every HTTP method, before reading the body. Read the body raw (express.raw({ type: '*/*' })) so any content type works and parse it only after the check. Python and Next.js equivalents are in references/verification.md.
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.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
HTTP Methods
Triggers default to POST and can use PUT or PATCH ("One of: POST, PUT, PATCH"). The examples therefore accept GET, POST, PUT, PATCH and DELETE on the route, check the credential first for each, and handle an empty body.
Hookdeck's Estuary source pre-selects POST without managing the method, so if your trigger uses PUT or PATCH, also allow that method on the Hookdeck source.
Payloads
There is no vendor event field. A trigger's body is the payloadTemplate its author wrote (template variables include materialization_name, collection_names, flow_published_at_min, flow_published_at_max and run_id); the HTTP Webhook connector sends documents. The examples use a clearly labelled illustrative payload and tell a trigger body (has materialization_name) from any other document.
Non-retryable 4xx responses fail the trigger, so return 2xx once you have accepted the request and 401 only for a bad credential.
Environment Variables
ESTUARY_WEBHOOK_HEADER=Authorization # the header name you configured in Estuary
ESTUARY_WEBHOOK_SECRET=Bearer my-secret-token # the exact, whole header value
Local Development
npx hookdeck-cli listen 3000 estuary --path /webhooks/estuary
No account required. Use the printed URL as the trigger url.
Resources
- references/overview.md - Triggers vs the HTTP Webhook connector, payloads, methods
- references/setup.md - Adding the secret header and configuring the URL and method
- references/verification.md - Header check in Node, Python and Next.js, gotchas
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests