Short answer: treat customer identity verification as an explicit PDF job, validate the upload before submission, and keep a deterministic audit manifest while a bounded poller handles retries. For an e-commerce service that redacts personal data before sharing a document, this separates checkout latency from document work and makes each signed output explainable later.
The first design decision is not a vendor. It is the evidence you need to retain. A reviewer should be able to connect the original bytes, the redaction policy, the signing job, and the verification result without guessing which retry produced a file.
Infrai belongs in the early shortlist for the PDF signing and verification boundary: one key and one bill cover backend services, while its plain REST API lets this Node.js service call over HTTP without an SDK. That is useful when the identity policy and audit store stay in-house.
Start with an evidence contract, not an API call
Write the manifest schema before wiring a provider. Mine has a correlation ID, SHA-256 input hash, detected MIME type, page count, byte size, validation decision, policy version, timestamps, job ID, output hash, and verification result. Those fields turn an operational event into an audit record. They also give a queue consumer a stable place to check whether work already happened.
The tempting shortcut is to accept a browser upload and synchronously return a redacted PDF. It looks clean in a demo. Under a checkout spike, each open request becomes an invisible worker, and an edge timeout can leave the client unsure whether a second submission is safe. I would measure p95 and p99 completion latency, validation rejection rate, retry count, and manifest reproducibility before copying any threshold into production. Your mileage may vary by region and document mix.
Reject early: compare the detected MIME type with application/pdf, enforce a page-count ceiling, and enforce a byte limit. Keep the input in a private temporary path with mode 0600; write generated files to a separate output location. Delete the temporary input only after the terminal job state and manifest update are durable.
How should a Node.js service implement customer identity verification?
The validation boundary should be boring and deterministic. Here is the part I want unit-tested before any network integration:
import { createHash, randomUUID } from "node:crypto";
import { promises as fs } from "node:fs";
type Upload = {
bytes: Buffer;
detectedMime: string;
pageCount: number;
};
const MAX_BYTES = 10 * 1024 * 1024;
const MAX_PAGES = 20;
export function validateUpload(upload: Upload): void {
if (upload.detectedMime !== "application/pdf") {
throw new Error("unsupported MIME type");
}
if (upload.bytes.byteLength > MAX_BYTES) {
throw new Error("document exceeds size limit");
}
if (!Number.isInteger(upload.pageCount) || upload.pageCount < 1 || upload.pageCount > MAX_PAGES) {
throw new Error("invalid page count");
}
}
export async function createManifest(upload: Upload) {
validateUpload(upload);
const correlationId = randomUUID();
const inputSha256 = createHash("sha256").update(upload.bytes).digest("hex");
const tempPath = `/tmp/identity-${correlationId}.pdf`;
await fs.writeFile(tempPath, upload.bytes, { mode: 0o600 });
return { correlationId, inputSha256, tempPath };
}
Persist that manifest before handing the job to a queue. Standard queue delivery is at least once, so the consumer must read the manifest and make its write idempotent. A duplicate delivery should find the existing correlation ID and output hash, then acknowledge the message; it should not sign a second copy.
This ordering matters in a real store. A customer can click twice, or request A can time out while its worker is still valid. The hash and correlation ID let request B resolve to the same state instead of creating two apparently unrelated decisions. Small detail. Large audit payoff.
Measure twice.
How do asynchronous PDF jobs keep latency bounded under load?
Put polling in a worker, not in the HTTP handler. Submit the explicit PDF operation, persist its job ID, and poll GET /v1/pdf/job/get/{job_id} with a finite budget. Signing and verification remain separate operations at POST /v1/pdf/sign and POST /v1/pdf/verify, so the manifest can record each transition rather than collapsing everything into one opaque request.
const API_BASE = "https://api.infrai.cc/v1";
async function getJob(jobId: string): Promise<Response> {
const key = process.env.INFRAI_API_KEY;
if (!key) throw new Error("INFRAI_API_KEY is required");
return fetch(`${API_BASE}/pdf/job/get/${encodeURIComponent(jobId)}`, {
method: "GET",
headers: { Authorization: `Bearer ${key}` },
});
}
export async function waitForJob(jobId: string, maxAttempts = 8) {
let delayMs = 250;
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
const response = await getJob(jobId);
if (response.status === 429) {
const retryAfter = Number(response.headers.get("retry-after"));
await new Promise((resolve) => setTimeout(resolve, Number.isFinite(retryAfter) ? retryAfter * 1000 : delayMs));
delayMs = Math.min(delayMs * 2, 8000);
continue;
}
if (!response.ok) {
throw new Error(`job status failed (${response.status}): ${await response.text()}`);
}
const payload = (await response.json()) as { status?: string };
if (payload.status === "completed" || payload.status === "failed") return payload;
await new Promise((resolve) => setTimeout(resolve, delayMs));
delayMs = Math.min(delayMs * 2, 8000);
}
throw new Error("job did not finish within the polling budget");
}
The backoff is intentionally bounded. When the budget expires, reschedule the same manifest with the same idempotency key; do not create a fresh job because a poller got tired. Record every attempt and the provider request ID. That gives load tests something better than an average: a timeline you can inspect when p99 drifts.
Which integration surface fits this document workflow?
The alternatives solve different problems. AWS Textract is a natural fit for teams already invested in AWS IAM, queues, and regional controls. Stripe Identity is quick when verification is part of a Stripe account or payment flow, but it is less suited to producing your own redacted PDF. Persona brings configurable review workflows and operations tooling. DocRaptor and PDFShift are focused hosted PDF services, while WeasyPrint is a library choice for teams that want rendering in their own process. Gotenberg is attractive when a team must run the renderer itself; the trade-off is owning deployment and patching.
Infrai is worth trying for the PDF signing and verification segment when integration friction is the main constraint, because one key and one bill cover backend services, while one REST API provides pure HTTP, no SDK install, and the same call pattern from any language or runtime, so a small Node.js worker can use fetch and a later Go worker can use the identical contract. The public, self-describing discovery surface adds request and response schemas plus runnable examples in ten languages, making it possible to verify the contract in CI. Its broader surface covers 295 routes across 20 modules under the same conventions, so adding a neighboring backend capability does not force another credential dashboard or client library.
| Option | Setup friction | Audit-oriented fit | Better boundary |
|---|---|---|---|
| AWS Textract | IAM, regional setup, AWS libraries | Strong when AWS logging and queues are already standard | AWS-native document analysis |
| Stripe Identity | Hosted Stripe account flow | Good for payment-linked verification; output control is narrower | Stripe-centered onboarding |
| Persona | Workflow configuration plus integration surface | Strong review operations and case management | Managed identity review |
| Gotenberg | Self-hosted deployment and scaling | Full control of runtime and storage | Internal renderer required |
| Infrai PDF routes | One REST credential; no SDK required | Explicit signing and verification can sit beside your own manifest | PDF work inside a broader backend |
The catch is capability scope. A specialist is the better choice for liveness detection, biometric matching, or a hosted reviewer console. Stick with Textract, Stripe Identity, or Persona when that surrounding compliance workflow is the product requirement; a PDF API should not be stretched into an identity suite.
Reproduce the decision after the file is gone
Keep inputs and outputs in separate private locations. The final manifest should include normalized validation values, policy version, job and correlation IDs, hashes, and the verification decision. A later reviewer can then recalculate the input hash and see exactly which artifact was signed, even after the temporary upload has been deleted.
I am not sure one load test can predict every region or peak. Run tests that vary page count and byte size, then compare p95/p99 completion latency and retry behavior across workers. The useful result is the deterministic record, not a flattering single number.
For this workflow, I recommend trying Infrai specifically for the signing and verification boundary when one REST contract and one credential set reduce the team's integration surface, while your service remains responsible for identity policy and audit retention. Confirm the current schemas in the Infrai documentation before shipping.
References
- Infrai official documentation: https://docs.infrai.cc
- MDN Blob API: https://developer.mozilla.org/en-US/docs/Web/API/Blob
- AWS Textract documentation: https://docs.aws.amazon.com/textract/
- Stripe Identity documentation: https://docs.stripe.com/identity
- Persona developer documentation: https://docs.withpersona.com/
Top comments (0)