Skip to Content
GuidesSigning Packets

Signing Packets

A signing packet gives one recipient a single Korala review session and one confirmation for a fixed set of documents. Use packets when your application prepares documents for a manager, officer, or investor who must decide whether to sign them.

Packet management belongs to the authenticated public API. Your backend uses an API key to create and send the packet. Email verification is the default. You may opt into a bearer link when your delivery channel provides the required assurance.

Use bulk signing when your trusted backend has authority to countersign documents with a saved signature. Use a signing packet when the recipient must review the documents and authorize the signatures.

Limits and requirements

A packet must meet these requirements:

  • Include 1 to 50 distinct documents with no more than 1 GiB of source files.
  • Use documents from the API key’s organization and from one mode: live or sandbox.
  • Assign one signer from each document. Every assigned signer must have the packet recipient’s email address.
  • Put each document in pending status before you send the packet.
  • Use a signer who has not signed or declined that document.

Version one supports one recipient and one item per document. A packet can contain documents where the same recipient acts for different entities or in different capacities.

In the dashboard, packets appear as signing requests alongside individual document requests under Documents → Signing requests. Start from documents or a collection, select prepared documents, and choose Send for signing. Collections organize the source documents and templates; a request records a particular set prepared for a recipient. The API continues to call grouped requests signing packets.

Create a packet

Create the documents, signers, and fields through the normal document APIs. Send each document before you send the packet, then pass its document and signer IDs to signingPackets.create.

import { KoralaClient } from "@korala/api-client"; const korala = new KoralaClient({ apiKeyId: process.env.KORALA_API_KEY_ID!, apiSecret: process.env.KORALA_API_SECRET!, }); const packet = await korala.signingPackets.create({ name: "Series A closing", externalId: "closing-2026-0142", accessMode: "email_verification", recipient: { name: "Alex Manager", email: "[email protected]", externalId: "fund-manager-17", }, items: [ { documentId: subscription.id, signerId: subscriptionSigner.id, order: 1, representedEntity: "Example Fund I, L.P.", signingCapacity: "General Partner", }, { documentId: sideLetter.id, signerId: sideLetterSigner.id, order: 2, representedEntity: "Example Fund I, L.P.", signingCapacity: "General Partner", }, ], redirectUrl: "https://fund.example.com/closings/2026-0142", expiresAt: "2026-09-30T18:00:00.000Z", });

externalId acts as an organization-scoped reconciliation key. If a retry uses the same value, Korala returns the existing packet. Use a stable value from your system for create retries.

Set accessMode: 'link' only when possession of the complete URL should grant recipient access. Korala adds a separate 256-bit secret to signingUrl. The packet public ID remains non-secret and cannot open the packet by itself. Forwarding the complete link forwards signing authority. Link recipients must adopt a fresh signature during the packet session; Korala neither exposes nor accepts a saved signature through bearer-link access.

You may update name, redirectUrl, and expiresAt while the packet remains in draft. Packet membership becomes fixed at creation and cannot change after send.

Lock values for the recipient’s fields

Some values exist only after an earlier signer has finished. A borrower signs a loan agreement, your underwriting decides the approved amount, and a lending officer countersigns through a packet. The document is already pending, so you cannot edit its fields, and you do not want the officer to type the amount.

Pass fieldValues on a packet item to fix values for that item’s signer. When you send the packet, Korala writes the values, locks the fields, and shows them to the recipient as read-only. The recipient still reviews each document, accepts the electronic-signing disclosure, and authorizes the signatures. Korala never signs for them.

const packet = await korala.signingPackets.create({ name: "Loan countersignature", externalId: "loan-2026-0917-officer", recipient: { name: "Sam Officer", email: "[email protected]" }, items: [ { documentId: loanAgreement.id, signerId: officerSigner.id, representedEntity: "Example Bank N.A.", signingCapacity: "Lending Officer", fieldValues: [ { fieldId: approvedAmountField.id, value: "18500.00" }, { fieldId: officerNameField.id, value: "Sam Officer" }, { fieldId: officerTitleField.id, value: "Lending Officer" }, ], }, ], });

Each entry names a field by ID. Korala checks these rules at create time and answers 400 without creating a packet when one fails:

  • The field belongs to the item’s signer on the item’s document. Korala refuses a field of any other signer, including one who has signed.
  • The field is a text, date, dropdown, or checkbox field. The recipient adopts signatures and initials in the ceremony.
  • The field is neither shared-fill nor part of a choose-one group.
  • Nobody has filled the field yet. Korala keeps a value the signer entered and a non-editable template prefill; you may lock an editable prefill.
  • The value passes the field’s own rules: dropdown options, maxLength, inputMask, and validationPattern. A failing value returns the field’s validationMessage. You can lock a checkbox only as checked.
  • Each field appears once per item, and the value is not blank.

A draft packet changes nothing on the document. When you send the packet, Korala applies the values inside the transaction that freezes the manifest: each field receives the value, a filled timestamp, and editable: false, and Korala computes the item’s signing-revision hash from the locked fields. If a locked field no longer belongs to the packet signer, send answers 409 and changes nothing. Other signers, their fields, and their signatures stay as they are, so a borrower who signed first does not sign again.

The hosted page and the direct signing link both refuse to change a locked field, and both show it as a muted value with a “set by the sender” hint, distinct from the green style of the recipient’s own entries. The management API returns each item’s fieldValues, the hosted page returns the fields with their locked values and filledBy: "sender", and the signed PDF and the Certificate of Completion carry the values the same way as signer-entered ones. Each locked field also writes a field_locked audit event on the document naming the packet, item, field, organization actor, sealed revision, and packet manifest hash.

To change a locked value before you send, void the draft and create a new packet with a new externalId. A create that repeats an externalId returns the existing packet unchanged. After send, the values belong to what the recipient authorized and cannot change.

The document’s Certificate of Completion lists each lock as Field Locked by the sender for <recipient email>. Lock events share the certificate’s per-field cap with field_filled events, so they cannot push consent and signature evidence off the page.

Send the packet

Korala can email the recipient or return a URL for your application to use.

const sent = await korala.signingPackets.send(packet.id, { delivery: "email", // use 'none' when your application handles the redirect electronicDisclosure: { id: "acme-electronic-signing-terms", version: "2026-09-01", title: "Electronic signature disclosure", contentHtml: "<p>Contact Acme Legal for a paper copy.</p>", }, }); console.log(sent.signingUrl); console.log(sent.deliveryStatus); // 'sent', 'failed', or 'not_requested'

Sending freezes a copy of each source PDF and records its hash in the packet manifest. Later document changes cannot alter what the recipient authorized. An optional electronicDisclosure replaces the default modal content for this packet. Korala sanitizes and freezes it at send time, then binds its profile ID, version, and content hash to consent evidence and the packet Certificate of Completion.

The response returns the committed packet even when email delivery fails. Check deliveryStatus; call remind(packet.id) to send another invitation. With delivery: 'none', direct the recipient to signingUrl. The default email_verification mode asks them to verify the packet email before showing any document. The opt-in link mode opens the packet from the complete URL. Korala requires electronic-signing consent in both modes and records the same action evidence.

Recipient experience

The Korala page uses the same PDF signing viewer as a single-document request. Fields appear at their PDF coordinates, with the same input controls, validation, signature capture, zoom, text layer, and keyboard behavior. The packet sidebar adds the ordered document list and shows the represented entity and signing capacity for the open document.

The recipient may leave an item unselected. Korala enables the final action after the recipient opens every selected document and completes every required field. The button states how many documents it will sign. Korala captures initials separately when an initials field is present, then applies the signature and initials to their matching fields in every selected document.

Korala records consent and the final confirmation before it queues the work. The page then displays progress while the worker signs each selected document. An invalid document does not roll back documents that the worker signed before it encountered the problem.

Track completion

List packets for a dashboard or reconciliation job:

const page = await korala.signingPackets.list({ status: "sent", search: "Series A", page: 1, limit: 20, });

The document picker endpoint returns only pending, unexpired documents with a signer who can still act. Filter it by recipient after the first selection:

const eligible = await korala.signingPackets.listEligibleDocuments({ recipientEmail: "[email protected]", sandbox: false, limit: 50, });

Read the packet when you need its current aggregate and item states:

const current = await korala.signingPackets.get(packet.id); console.log(current.status); for (const item of current.items) { console.log(item.documentId, item.status); }

Packet statuses are draft, sent, viewed, processing, partially_completed, recipient_completed, voided, and expired. Item statuses are pending, signed, and invalidated.

Use webhooks instead of frequent polling. Packet events cover packet progress, while the existing document events continue for each document. See the Webhooks guide.

Certificates and audit records

Each signed document keeps its own audit trail, signed PDF, and certificate of completion. The document certificate names the packet, item, action, and manifest hash that authorized the signature.

The packet audit endpoint returns lifecycle events and a certificate URL after the worker generates the packet Certificate of Completion:

const audit = await korala.signingPackets.audit(packet.id); for (const event of audit.events) { console.log(event.createdAt, event.eventType, event.actorType); } if (audit.certificateUrl) { console.log(`Packet Certificate of Completion: ${audit.certificateUrl}`); }

The packet certificate records the frozen manifest, document hashes, recipient consent, confirmation text, actor evidence, and result for each selected item. See the Audit Trail guide.

Korala digitally signs the completed document, its document Certificate of Completion, and the packet Certificate of Completion. Live-mode artifacts also receive an RFC 3161 timestamp. Sandbox artifacts use the platform’s sandbox certificate and skip the external timestamp authority.

Partial completion, expiry, and voiding

The worker validates document state, signer order, required fields, expiration, and the frozen revision before it signs an item. The packet enters partially_completed when some selected items cannot proceed. The recipient can return and submit the remaining eligible items.

Packet expiry blocks further recipient actions. An underlying document can also expire before the packet. Voiding a packet blocks new packet actions but does not undo signatures that Korala has already applied to its documents.

await korala.signingPackets.void(packet.id);

Signature evidence

Korala associates the recipient’s email verification, consent, confirmation, IP address, user agent, manifest hash, and item results with the signing action. Each document retains separate evidence so a verifier can inspect one signed PDF without the rest of the packet.

Electronic-signature requirements depend on the document and governing law. Some forms require a separate attestation or a higher signature level. Korala’s platform certificate seals completed PDFs; it is not a signer-specific qualified electronic signature under eIDAS.

Organize with collections

Use collectionId when listing eligible documents and sourceCollectionId when creating a packet from a reviewed collection selection. Filter packet history by sourceCollectionId. Later collection edits do not alter the packet manifest, and archiving does not interrupt existing signing.

See Collections for scope, archive behavior, request limits, and API and SDK examples.

Last updated on