Skip to main content

Decoupled Submission Modes

By default the SDK captures, packages, and submits documents to the Veryfi API for you. If you need to forward the document through your own backend — to attach business data, hit a different endpoint, or queue the work — you can run the SDK in a decoupled mode where you receive the captured artifact (and an S3 path to the package) and submit it yourself.

Three configuration knobs control where the responsibility lives:

OptionDefaultEffect
packageModefalseWhen true, captured scans are bundled into a single zip and uploaded to S3. The SDK gives you a package_path you can forward instead of raw bytes. When false (default), the legacy per-crop /rest/send-cropped flow runs and the partner-facing payload is the base64 image only.
enableSubmittrueWhen true, the SDK renders a Submit button and runs the submission flow on click. When false, no button is shown — the SDK instead delivers the result directly to your showCamera setter.
customSubmitHandlernullWhen set, the SDK calls your handler instead of POSTing to Veryfi when the Submit button is clicked. Implies enableSubmit: true.

Combined, these give you three integration shapes:

  1. Default — SDK submits to Veryfi.
  2. Custom submit handler — SDK keeps the Submit button UX, you take over the network call.
  3. Fully decoupled (no submit button) — SDK uploads to S3 and hands the artifacts to your showCamera setter; your UI owns the rest.

Mode 1: Default (SDK submits)

Nothing to opt into. Initialize, attach handlers, show the camera. customData configured on the SDK is automatically merged into the request body.

await VeryfiLens.init("YOUR_CLIENT_ID", {
lensFlavor: "document",
torchButton: true,
exitButton: true,
customData: {
external_id: "INV-2026-001",
tags: ["accounting", "q2"],
category: "Travel",
},
});

VeryfiLens.onSuccess((result) => {
console.log("Processed by Veryfi:", result);
});

VeryfiLens.onFailure((error) => {
console.error("Submission failed:", error);
});

VeryfiLens.showCamera();

What hits /api/document:

{
"doc_type": "documents",
"image": "<base64-encoded image>",
"external_id": "INV-2026-001",
"tags": ["accounting", "q2"],
"category": "Travel"
}

Per-crop, the SDK also posts to /rest/send-cropped to fire document_ready server-side. That call carries the crop image and device metadata only — your customData does not ride along with it (it's attached only to the final /api/document submission).

info

The keys you can put in customData are the same fields accepted by the corresponding Process Document endpoint for the flavor you're using:

  • lensFlavor: "document"Process a Document (e.g. external_id, tags, category, auto_delete, boost_mode, async, device_user_uuid, parser, …)
  • lensFlavor: "checks"Process a Check
  • lensFlavor: "anydocs" / "prescription_label"Process a ∀Doc

Any field listed under "Request Body" on those pages can be added to customData and the SDK will forward it. Reserved internal fields (image, file_data, file_name, package_path, bucket, upload_id, package_metadata, device_data, client_name, doc_type, resource) are managed by the SDK and should not be set in customData.

Mode 2: Custom submit handler

Keep the Submit button, but replace the call to Veryfi. Your handler runs on click and receives the captured artifact — plus, when you opt into packageMode: true, the S3 details for the uploaded package zip.

await VeryfiLens.init("YOUR_CLIENT_ID", {
lensFlavor: "document",
packageMode: true,
customSubmitHandler: async (image, packageInfo) => {
await fetch("https://your-backend.example.com/documents", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
bucket: packageInfo?.bucket,
package_path: packageInfo?.package_path,
file_name: packageInfo?.file_name,
package_id: packageInfo?.package_id,
image,
external_id: "INV-2026-001",
tags: ["accounting", "q2"],
}),
});
},
});

VeryfiLens.showCamera();

Handler signature:

type CustomSubmitHandler = (
image: string | Blob,
packageInfo?: {
bucket?: string;
package_path?: string;
}
) => void | Promise<void>;

Behavior by packageMode:

packageModeFirst argSecond arg
false (default)base64 image (or base64 ZIP for prescription_label)null — there is no package, the per-crop /rest/send-cropped flow already shipped each crop per-capture
truebase64 image (or video Blob for prescription_label)populated packageInfobucket, package_path, file_name, package_id
info

customData set via setCustomData() or init({ customData }) is not passed to your handler — you're the one who configured it, so you already have it. Attach it to your own request alongside the SDK-provided artifacts.

Mode 3: Fully decoupled (no submit button)

Set enableSubmit: false together with packageMode: true. The SDK uploads the package to S3 immediately on the final capture and hands the result to your showCamera setter. There's no Submit button, no onSuccess, no call to Veryfi from the SDK — the SDK's job ends at "here is the captured image and the S3 details for the package". What happens next is entirely up to your code: submit it, queue it, preview it, throw it away.

await VeryfiLens.init("YOUR_CLIENT_ID", {
lensFlavor: "document",
packageMode: true,
enableSubmit: false,
customData: {
external_id: "INV-2026-001",
tags: ["accounting", "q2"],
},
});

VeryfiLens.showCamera((result) => {
if (result && typeof result === "object" && (result.package_path || result.package_id)) {
// SDK is done. `result` is yours — store it, display it, send it, anything.
setCapturedDocument(result);
}
});

Setter payload shape:

{
bucket: string;
package_path: string;
file_name: string;
package_id: string;
image: string | Blob;
}
tip

The SDK calls removeOverlay() immediately after firing this setter, which tears down the camera DOM. Your UI should react to the setter call to render the next screen — relying on onSuccess will not work in this mode (the SDK never calls /api/document).

When you later want to forward it to your backend — for example from a "Send" button on your own preview screen — you have everything you need:

async function submitToYourBackend(captured) {
const customData = VeryfiLens.getCustomData();

await fetch("https://your-backend.example.com/documents", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
bucket: captured.bucket,
package_path: captured.package_path,
file_name: captured.file_name,
package_id: captured.package_id,
...customData,
}),
});
}

Submitting the package directly to Veryfi

The package_path you receive is an S3 key already uploaded under your client. You can hand it off to your own server (as above) or post it straight to the Veryfi Process Document API yourself by passing it instead of file_data:

await fetch("https://api.veryfi.com/api/v8/partner/documents/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"CLIENT-ID": "YOUR_CLIENT_ID",
"AUTHORIZATION": "apikey YOUR_USERNAME:YOUR_API_KEY",
},
body: JSON.stringify({
package_path: captured.package_path,
external_id: "INV-2026-001",
tags: ["accounting", "q2"],
}),
});

See the full request/response reference for the endpoint matching your lensFlavor:

caution

The Process Document API key (apikey USERNAME:API_KEY) must never be exposed in browser code. Make the request from your own backend.

Multi-capture flavors

For flavors with more than one capture per submission, the SDK only uploads once — at the final capture — to avoid re-uploading bytes for every page.

FlavorPer-capture setter fires withFinal setter fires with
checks (front + back)front image onlyfront + back image + packageInfo
anydocs (N pages)per-page image only"Done" click → final image + packageInfo
document, long_document, prescription_label, caps, code_strips, gallery uploadn/a (single capture)image + packageInfo
info

For anydocs in this mode the page-controls strip ("Add another page" / "Done") is shown even though enableSubmit is false, and the action button is labelled Done instead of Submit. Detect completion by checking for package_path / package_id on the setter payload — earlier per-page fires won't carry them.

Custom data across modes

customData is attached to a submission in exactly one place — wherever the request to your processing endpoint is constructed.

ModeWho builds the /api/document (or your backend) requestcustomData is attached by
DefaultSDKSDK — automatically spread into the request body
customSubmitHandlerYour handlerYou — customData is not passed in, attach it from your own state
Fully decoupled (!enableSubmit)Your app code (whenever you decide to submit)You — read it back via VeryfiLens.getCustomData() if needed

You can update custom data at any time:

VeryfiLens.setCustomData({
external_id: "INV-2026-002",
tags: ["accounting", "q3"],
});

The data is stored on the SDK and used on the next submission — there is no need to re-init.

packageMode: false

This is the SDK's default submission flow:

  • The Submit button (or your customSubmitHandler) receives the base64 image directly.
  • customSubmitHandler's second argument is null — no aggregated package info is produced.
await VeryfiLens.init("YOUR_CLIENT_ID", {
lensFlavor: "document",
customSubmitHandler: (image, packageInfo /* always null here */) => {
submitBase64ToYourBackend(image);
},
});

Quick reference

GoalpackageModeenableSubmitcustomSubmitHandlerWhere to read the result
Let Veryfi process the document (default per-crop flow)falsetruenullonSuccess
Per-crop base64 flow, your backendfalsetruesetinside the handler (packageInfo is null)
Let Veryfi process the document via S3 packagetruetruenullonSuccess
Forward the package to your backend, keep Submit UXtruetruesetinside the handler
No Submit button, your UI continues after uploadtruefalsenullshowCamera setter (look for package_path)