document parsing webhookwebhook document processingautomate document pipeline

Webhook-Driven Document Processing: Build Automated Pipelines with Dokyumi

March 16, 2026 · Updated

Dokyumi webhooks deliver completed extraction data for documents submitted through a configured upload site. Direct POST /api/v1/extract requests are synchronous and do not accept a per-request webhook_url.

When Dokyumi sends a webhook

  1. Create or edit an upload site in Sites → Settings.
  2. Set the site webhook URL to a public HTTPS endpoint.
  3. A user submits a document through that site.
  4. After a successful extraction, Dokyumi sends one extraction.completed event.

Every plan includes at least one upload site, with plan-specific site limits. API-key uploads, dashboard uploads, and hard extraction failures do not produce this webhook event.

Payload contract

{
  "event": "extraction.completed",
  "extraction_id": "6f8c2d4a-7b31-4e95-9a20-c1d7f6b84210",
  "site_id": "3e5b7c19-8d42-4fa1-a760-c2e9d4b18306",
  "data": {
    "invoice_number": "INV-2026-0342",
    "vendor_name": "Acme Corp",
    "total_amount": 4250.00
  },
  "timestamp": "2026-08-23T19:12:00.000Z"
}

Use extraction_id as the idempotency key. The data object contains the extracted schema fields directly; it is not nested under result.

Verify signed deliveries

When a signing secret has been provisioned for the site, Dokyumi sends X-Dokyumi-Signature as the raw lowercase hexadecimal HMAC-SHA256 digest of the exact request body. The header does not include a sha256= prefix. If a configured site sends an empty signature header, contact Dokyumi support to provision or rotate its secret before treating the signature as an authentication control.

import express from 'express';
import crypto from 'node:crypto';

const app = express();
app.use('/webhooks/dokyumi', express.raw({ type: 'application/json' }));

app.post('/webhooks/dokyumi', async (req, res) => {
  const signature = req.header('x-dokyumi-signature') || '';
  const expected = crypto
    .createHmac('sha256', process.env.DOKYUMI_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (!/^[0-9a-f]{64}$/i.test(signature) || !crypto.timingSafeEqual(
    Buffer.from(signature, 'utf8'),
    Buffer.from(expected, 'utf8')
  )) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(req.body.toString('utf8'));
  if (event.event !== 'extraction.completed') {
    return res.status(400).json({ error: 'Unsupported event' });
  }

  await queue.add('process-dokyumi-extraction', {
    extractionId: event.extraction_id,
    siteId: event.site_id,
    data: event.data,
  });

  return res.status(200).json({ received: true });
});

For production code, compare equal-length buffers and reject a missing signature before calling timingSafeEqual. Keep the raw request body: parsing and re-serializing JSON changes the signed bytes.

Acknowledge quickly and process idempotently

Validate the event, await a durable queue write, and only then return a 2xx response. Do database writes and downstream API calls in the worker. Upsert on extraction_id so a retry cannot create duplicate bills, leads, or records.

async function processDokyumiEvent(event) {
  await db.extractions.upsert({
    where: { extraction_id: event.extractionId },
    create: {
      extraction_id: event.extractionId,
      site_id: event.siteId,
      data: event.data,
    },
    update: { data: event.data },
  });
}

Retries and operational limits

Dokyumi makes the initial delivery and retries an undelivered event during a background sweep roughly every ten minutes, up to six total attempts. There is currently no self-service replay button. Fix the receiver before the retry window closes; if it has passed, contact support with the extraction ID.

Your endpoint should accept duplicate deliveries, log extraction_id, return a non-2xx response only when a retry is useful, and alert on events that never reach your durable queue.

Direct API batches are different

For server-to-server batches sent to POST /api/v1/extract, use bounded concurrency and store each synchronous response. Send the multipart file and optional schema slug. A direct API request does not trigger a site webhook.

See the API reference for the response envelope, the bulk-processing guide for queue design, and the Zapier guide for no-code workflows. You can test a site workflow with the invoice parser or map it to an accounts-payable workflow.

These articles are selected from the same editorial cluster, not generated from keyword overlap.

Put build and ship api pipelines to work

Use the API and webhook reference →

Confirm request fields, response data, validation, confidence, and webhook signing.

Trace an accounts-payable workflow →

See how schema, endpoint, confidence review, and ledger delivery fit together.

Build the first endpoint →

Create a schema and test the pipeline against a real source document.

Test the extraction on your own documents

25 free credits each month. One credit covers a document up to 5 pages; self-serve documents can be up to 50 pages. No credit card required.