Agent skill

Reducto Webhooks Skill

Receive and verify Reducto webhooks for async document parsing jobs. Use when setting up Reducto webhook handlers, debugging Svix signature verification (svix-id, svix-timestamp, svix-signature), or handling async.update job notifications with status Completed or Failed from /parse_async, /extract_async, /split_async, or /pipeline_async.

Install this skill

npx skills add hookdeck/webhook-skills --skill reducto-webhooks


Reducto (document parsing / extraction API) delivers async job notifications through Svix. When a job submitted with async.webhook.mode = "svix" finishes, Reducto sends an async.update webhook whose body reports the job status (Completed or Failed) and job_id.

When to Use This Skill

  • How do I receive Reducto webhooks for async parse/extract jobs?
  • How do I verify Reducto webhook signatures (svix-signature)?
  • How do I handle a Reducto job that Completed or Failed?
  • Why is my Reducto webhook signature verification failing?
  • How do I pass metadata through a Reducto async job and read it in the webhook?

Verification (core)

Reducto webhooks use the Svix scheme. Signed content is ${svix-id}.${svix-timestamp}.${raw_body}, HMAC-SHA256 keyed with the base64-decoded bytes after whsec_, base64-encoded, sent as space-delimited v1,<sig> entries. The official svix SDK (Node and Python) handles all of it, including the 5-minute timestamp tolerance and constant-time comparison.

import { Webhook } from 'svix';  // svix 2.x is ESM-only

const wh = new Webhook(process.env.REDUCTO_WEBHOOK_SECRET); // "whsec_..." from Svix dashboard
wh.verify(rawBody, {                                        // raw Buffer/string, NOT parsed JSON
  'svix-id': req.headers['svix-id'],
  'svix-timestamp': req.headers['svix-timestamp'],
  'svix-signature': req.headers['svix-signature'],
}); // throws WebhookVerificationError on bad signature / stale timestamp

// svix 2.x verify() returns undefined — parse the raw body after it passes
const { status, job_id, metadata } = JSON.parse(rawBody.toString('utf8'));

Python uses the same API: Webhook(secret).verify(body_bytes, headers) from svix.webhooks, raising WebhookVerificationError.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Webhook Event and Payload

Reducto sends a single Svix event type, async.update, for every async job. The body has no type field — branch on status:

statusTriggered WhenCommon Use Cases
CompletedAsync job finished successfullyFetch results with client.job.get(job_id), store output
FailedAsync job failedLog, alert, retry the job
{
  "status": "Completed",
  "job_id": "a1b9090e-c9ae-420b-9726-f658afbbe338",
  "metadata": { "user_id": "123", "document_type": "invoice" }
}

The webhook only carries status — retrieve the actual results via the Reducto API using job_id. metadata echoes whatever you passed in async.metadata.

Environment Variables

# Svix signing secret for your endpoint (starts with whsec_)
REDUCTO_WEBHOOK_SECRET=whsec_xxxxx

Get it in Reducto Studio → Webhooks (opens the Svix dashboard) → your endpoint → Signing Secret.

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 reducto --path /webhooks/reducto

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Oct 28, 2026

View on GitHub →