Skip to main content

QR Capture Guide

Generate a QR code on your desktop, let users scan it with their phone to capture documents, and receive results via webhooks — all without installing the Lens SDK on the desktop side.

info

Prerequisites:

  • CLIENT ID, USERNAME, and API KEY from the Keys section in Settings
  • Lens For Browser enabled for your account (confirm with Customer Support)
  • A publicly reachable URL for receiving webhooks (e.g. via ngrok for local development)

Authorization

Every request to the QR generation API requires two headers:

HeaderValue
CLIENT-IDYour Client ID
AUTHORIZATIONapikey {username}:{api_key}

Generate a QR Code

Send a POST request to your Lens for Browser backend:

curl -X POST https://lens.veryfi.com/api/generate_qr \
-H "Content-Type: application/json" \
-H "CLIENT-ID: your_client_id" \
-H "AUTHORIZATION: apikey your_username:your_api_key" \
-d '{
"client_id": "your_client_id",
"external_id": "ORDER-12345",
"mode": "document",
"expires_in": 3600,
"is_async": true
}'

Response

{
"session_id": "c01af7da-ce5f-43a2-abc4-eb21e2e82f61",
"external_id": "ORDER-12345",
"url": "https://lens-pwa.veryfi.com/capture/scan?client_id=...&session=...",
"qr_code_base64": "data:image/png;base64,iVBORw0KGgo...",
"expires_in": 3600
}

Display the QR by setting qr_code_base64 as the src of an <img> tag:

const img = document.createElement("img");
img.src = data.qr_code_base64;
img.width = 280;
img.height = 280;
document.body.appendChild(img);

Request Parameters

ParameterTypeRequiredDefaultDescription
client_idstringYesYour Veryfi Client ID
external_idstringNoauto-generatedYour own correlation ID — used to match webhook results back to this session
modestringNo"document"Capture mode: "document" (receipts/invoices) or "anydocs"
blueprintstringNoBlueprint slug for anydocs mode (e.g. "us_health_insurance_card")
expires_innumberNo3600QR code validity in seconds (60–86400)
base_urlstringNoBase URL of the app that hosts the capture page (used to build the QR link)
is_asyncboolNofalseWhen true, document processing happens asynchronously and results are delivered via webhook

Capture Modes

  • document — Optimized for receipts and invoices. Single-page capture with automatic document detection.
  • anydocs — Flexible multi-page capture. Supports blueprints for structured data extraction (insurance cards, IDs, etc.). Use blueprint to pre-select a template.

Receiving Webhooks

After the user scans the QR and submits a document, Veryfi processes it and sends a webhook to your configured URL.

Webhook Payload

{
"event": "document.created",
"data": {
"id": 8724086,
"created": "2026-03-26T18:30:00Z"
}
}

The webhook contains the document ID but not the full document data. Fetch the complete document using the ID:

curl -X GET https://api.veryfi.com/api/v8/partner/documents/8724086 \
-H "CLIENT-ID: your_client_id" \
-H "AUTHORIZATION: apikey your_username:your_api_key"

For anydocs, use the /any-documents/ endpoint instead:

curl -X GET https://api.veryfi.com/api/v8/partner/any-documents/8724086 \
-H "CLIENT-ID: your_client_id" \
-H "AUTHORIZATION: apikey your_username:your_api_key"

Webhook Event Types

Event PrefixDocument TypeFetch Endpoint
document.*Receipts / Invoices/api/v8/partner/documents/{id}
anydocument.*Any Documents/api/v8/partner/any-documents/{id}
check.*Checks/api/v8/partner/checks/{id}

Setting Up Webhooks Locally

For local development, use ngrok to expose your webhook endpoint:

ngrok http 3000

Then configure the webhook URL in your Veryfi dashboard under Settings > Keys > Webhook URL:

https://a1b2c3d4.ngrok-free.app/api/webhook

Full Integration Example

A minimal server-side handler that generates a QR code, receives the webhook, fetches the full document, and pushes the result to the frontend via Server-Sent Events:

import express from "express";
import { EventEmitter } from "events";

const CLIENT_ID = process.env.CLIENT_ID;
const USERNAME = process.env.USERNAME;
const API_KEY = process.env.API_KEY;
const LFB_BASE_URL = process.env.LFB_BASE_URL;
const VERYFI_API_BASE = "https://api.veryfi.com/api/v8/partner";

const app = express();
app.use(express.json());

const emitter = new EventEmitter();

const AUTH_HEADERS = {
"CLIENT-ID": CLIENT_ID,
AUTHORIZATION: `apikey ${USERNAME}:${API_KEY}`,
};

const EVENT_ENDPOINTS = {
document: "documents",
anydocument: "any-documents",
check: "checks",
};

// 1. Generate QR
app.post("/api/generate-qr", async (req, res) => {
const { external_id, mode, blueprint, expires_in } = req.body;

const response = await fetch(`${LFB_BASE_URL}/api/generate_qr`, {
method: "POST",
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
body: JSON.stringify({
client_id: CLIENT_ID,
external_id: external_id || `session-${Date.now()}`,
mode: mode || "document",
...(blueprint ? { blueprint } : {}),
expires_in: expires_in || 3600,
is_async: true,
}),
});

const data = await response.json();
res.json(data);
});

// 2. Receive webhook → fetch full document → push to frontend
app.post("/api/webhook", async (req, res) => {
const payload = req.body;
const event = payload.event || "";
const documentId = (payload.data || payload).id;

if (!documentId) return res.json({ status: "ok" });

const prefix = event.split(".")[0] || "any-documents";
const endpoint = EVENT_ENDPOINTS[prefix] || "any-documents";

const docResponse = await fetch(
`${VERYFI_API_BASE}/${endpoint}/${documentId}`,
{ headers: AUTH_HEADERS }
);
const document = await docResponse.json();
const externalId = document.meta?.external_id || document.external_id;

emitter.emit("webhook", { external_id: externalId, document });
res.json({ status: "ok" });
});

// 3. SSE stream filtered by external_id
app.get("/api/webhook-events", (req, res) => {
const externalId = req.query.external_id;

res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
});

res.write(`data: ${JSON.stringify({ type: "connected" })}\n\n`);

const onEvent = (event) => {
if (!externalId || event.external_id === externalId) {
res.write(`data: ${JSON.stringify(event)}\n\n`);
}
};

emitter.on("webhook", onEvent);
req.on("close", () => emitter.off("webhook", onEvent));
});

app.listen(3000);

On the frontend, subscribe to the SSE stream after generating the QR:

const eventSource = new EventSource(
`/api/webhook-events?external_id=${encodeURIComponent(externalId)}`
);

eventSource.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === "connected") return;

// Document received — show result, close stream
console.log("Document:", data.document);
eventSource.close();
};

Demo Project

A complete working example with a Vite frontend and Express backend is available in the lfb-qr-demo repository.

Next Steps