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:
| Option | Default | Effect |
|---|---|---|
packageMode | false | When 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. |
enableSubmit | true | When 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. |
customSubmitHandler | null | When 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:
- Default — SDK submits to Veryfi.
- Custom submit handler — SDK keeps the Submit button UX, you take over the network call.
- Fully decoupled (no submit button) — SDK uploads to S3 and hands the artifacts to your
showCamerasetter; 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).
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 ChecklensFlavor: "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:
packageMode | First arg | Second 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 |
true | base64 image (or video Blob for prescription_label) | populated packageInfo — bucket, package_path, file_name, package_id |
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;
}
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:
lensFlavor: "document"/"long_document"→ Process a DocumentlensFlavor: "checks"→ Process a ChecklensFlavor: "anydocs"/"prescription_label"→ Process a ∀Doc
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.
| Flavor | Per-capture setter fires with | Final setter fires with |
|---|---|---|
checks (front + back) | front image only | front + back image + packageInfo |
anydocs (N pages) | per-page image only | "Done" click → final image + packageInfo |
document, long_document, prescription_label, caps, code_strips, gallery upload | n/a (single capture) | image + packageInfo |
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.
| Mode | Who builds the /api/document (or your backend) request | customData is attached by |
|---|---|---|
| Default | SDK | SDK — automatically spread into the request body |
customSubmitHandler | Your handler | You — 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 base64imagedirectly. customSubmitHandler's second argument isnull— no aggregated package info is produced.
await VeryfiLens.init("YOUR_CLIENT_ID", {
lensFlavor: "document",
customSubmitHandler: (image, packageInfo /* always null here */) => {
submitBase64ToYourBackend(image);
},
});
Quick reference
| Goal | packageMode | enableSubmit | customSubmitHandler | Where to read the result |
|---|---|---|---|---|
| Let Veryfi process the document (default per-crop flow) | false | true | null | onSuccess |
| Per-crop base64 flow, your backend | false | true | set | inside the handler (packageInfo is null) |
| Let Veryfi process the document via S3 package | true | true | null | onSuccess |
| Forward the package to your backend, keep Submit UX | true | true | set | inside the handler |
| No Submit button, your UI continues after upload | true | false | null | showCamera setter (look for package_path) |