---
title: "Handwriting OCR API with webhooks: upload, process and export"
canonical: "https://www.handwritingocr.com/blog/handwriting-ocr-api-webhooks"
pubDate: "2026-09-25T00:00:00.000Z"
description: "Build an asynchronous handwriting OCR workflow with a file upload, signed completion webhook and result export. Includes curl and Node.js examples."
author: "Sam Prentice"
---

An OCR API workflow is asynchronous. Your application submits a document, stores the returned document ID, waits for processing to finish, and then saves or exports the result. A webhook is the cleanest way to receive that completion event without repeatedly polling the API.

This guide covers the complete path:

```text
PDF or image -> upload -> document ID -> signed webhook -> result -> your system
```

API access is available on every Handwriting OCR plan, including the five-page free trial. Start with the [API reference](/api/docs) when you need the complete endpoint and response schema.

## 1. Create an API token

Create a token in [Settings, API](https://dashboard.handwritingocr.com/settings?tab=api) and keep it in a secret manager or environment variable. Do not put it in browser-side JavaScript, source control or a public automation template.

The examples below read the token from `HANDWRITING_OCR_TOKEN`.

## 2. Upload the document and request a webhook

Send the document as multipart form data to the v3 documents endpoint. The `action` controls the workflow:

- `transcribe` for complete text
- `tables` for tabular data
- `extractor` with an `extractor_id` for selected fields

Pass `webhook_url` on the request when this job needs a specific callback. You can instead configure one global URL in [Document settings](https://dashboard.handwritingocr.com/settings?tab=documents).

```bash
curl --request POST 'https://api.handwritingocr.com/v3/documents' \
  --header "Authorization: Bearer $HANDWRITING_OCR_TOKEN" \
  --form 'action=transcribe' \
  --form 'file=@./scanned-notes.pdf' \
  --form 'webhook_url=https://example.com/webhooks/handwriting-ocr'
```

The accepted response includes a document ID and a queued status. Store that ID beside your own job or customer reference. It is the key used to correlate the completion event with the upload that started it.

## 3. Verify the webhook before trusting it

Webhook requests include an `X-Signature` header generated with HMAC-SHA256. Verify the signature against the raw request body using the secret from Document settings before parsing or acting on the payload.

In Node.js with Express:

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

const app = express();

app.post(
  '/webhooks/handwriting-ocr',
  express.raw({ type: 'application/json' }),
  (request, response) => {
    const received = request.get('X-Signature') ?? '';
    const expected = `sha256=${crypto
      .createHmac('sha256', process.env.HANDWRITING_OCR_WEBHOOK_SECRET)
      .update(request.body)
      .digest('hex')}`;

    const receivedBytes = Buffer.from(received, 'utf8');
    const expectedBytes = Buffer.from(expected, 'utf8');
    const valid =
      receivedBytes.length === expectedBytes.length &&
      crypto.timingSafeEqual(receivedBytes, expectedBytes);

    if (!valid) return response.sendStatus(401);

    const event = JSON.parse(request.body.toString('utf8'));
    // Queue internal work using the document ID. Respond promptly.
    response.sendStatus(204);
  },
);
```

Keep the raw request body available until verification is complete. Parsing and serialising the JSON first can change whitespace or key ordering and produce a different signature.

## 4. Make completion handling idempotent

Production webhook handlers should expect retries and duplicate delivery. Treat the document ID as an idempotency key:

1. Look up the document ID in your jobs table.
2. Ignore a completion that has already been handled successfully.
3. Record the event before starting a long download or import.
4. Return a fast success response, then continue the expensive work in a queue.

Do not use “the latest processed document” to decide which result belongs to which upload. Explicit document-ID correlation prevents one customer's result or one concurrent batch item from being attached to the wrong workflow.

## 5. Download or store the result

Once the job is processed, request the document in the format required by the workflow. Transcription can return TXT, DOCX, PDF or JSON. Tables can return XLSX or JSON. Custom Extractors can return XLSX, CSV or JSON.

Use the [download-result endpoint](/api/docs/download-result) for the exact path and response options. Store the export in your own application, object store, CRM or document-management system before the account's retention window expires.

The default automatic deletion period is seven days and can be configured from 15 minutes to 14 days. The upload request also supports `delete_after` when a job needs a specific shorter window.

## Polling as a recovery path

For a first test, you can request the document status by ID until it reaches `processed`. In production, avoid a tight loop:

- use the webhook as the normal completion path
- add exponential backoff when polling
- respect `Retry-After` after a `429` response
- stop retrying on a terminal failure
- reconcile delayed jobs with the document-list endpoint

Starter and Pro accounts allow two requests per second. Business allows five requests per second, with higher limits available by agreement. Webhooks keep status checks from consuming most of that allowance.

## Failure and review boundaries

A reliable OCR integration also defines what happens after recognition:

- refund and retry logic for failed pages
- review rules for names, identifiers, totals and other critical values
- supported file and size checks before upload
- an audit record connecting the source, document ID and stored result
- deletion only after the export has been saved successfully

The API accepts PDF, JPG, PNG, GIF, HEIC and TIFF files up to 20 MB. For large collections, split source material into manageable PDFs of roughly 100 pages and test representative documents before starting the full run.

## Start with one end-to-end document

The first useful API test is not merely a successful upload. Confirm that one real document reaches the webhook, passes signature verification, correlates to the correct internal job, produces the required export and is stored before deletion.

[Create a free account](https://dashboard.handwritingocr.com/register) and use the included five pages for that end-to-end test, then move to the complete [API documentation](/api/docs).
