Signing Flow

KarmaFlow ships with a fully integrated, self-hosted e-signature workflow. There is no third-party signing vendor involved — documents are signed inside the KarmaFlow signing portal, signatures are stamped onto the PDF by the platform, and a tamper-evident audit trail is embedded directly into the final document.

Status State Machine

Every document moves through a defined sequence of statuses:

drafting → draft → pending_review → approved → out_for_signature →
   partially_signed → signed
                  ↘ declined
                  ↘ voided
                  ↘ expired

Signer Configuration

Each signer record carries:

Sequential vs Parallel Routing

When you click Send for signature, the signing mode set on the document determines how invites are dispatched:

You can choose sequential or parallel per document; or lock the signing mode at the agent level if every document of that type should use the same routing.

What the Signer Experiences

Each signer receives an email with a unique, signed URL. The URL is shaped like:

/sign/{tenantId}/{token}

The token is generated as 32 bytes of cryptographically random data, then hashed before storage — KarmaFlow never stores raw signing tokens. The plaintext token only exists in the recipient's email. Each token is single-use per signer and expires after 30 days by default.

When the signer clicks their link, they land on a clean, brandable signing portal that requires no account:

  1. Document preview — The full document, rendered in HTML
  2. Consent gate — A required checkbox: "I consent to do business electronically and agree that my electronic signature has the same legal effect as a handwritten signature. I understand I am entering into a legally binding agreement." This is captured per-signer with timestamp, IP address, user agent, and browser language — the ESIGN Act consent record
  3. Identity / attribution — The signer types their full legal name and/or draws their signature on the canvas
  4. Submit — Clicking Sign & Submit constitutes the affirmative "intent to sign" act under ESIGN/UETA
  5. Decline — Alternative path; the document moves to declined and the flow halts

Final Stamping

When the last signer completes:

  1. The system fetches the latest unsigned PDF from cloud storage
  2. pdf-lib stamps each resolved signing field at its pixel coordinates:
    • signature — embeds the drawn signature PNG (or falls back to typed name if no canvas drawing)
    • initial — stamps signer's initials
    • date — stamps the signed date in ISO format
    • text — stamps the field value
    • checkbox — stamps a checked/unchecked glyph
  3. A Certificate of Completion page is appended. The certificate lists:
    • Document title and ID
    • Completion timestamp
    • Every signer with their name, email, role, signed timestamp, and IP
    • The complete audit trail (most recent 30 events) with timestamps and IPs
    • The pre-certificate SHA256 hash of the stamped PDF — your tamper-evidence anchor
  4. The final hash of the signed-plus-certificate PDF is computed and stored on the document record (finalPdfSha256)
  5. The signed PDF is uploaded to cloud storage under the document's versioned prefix
  6. A signed copy is automatically emailed to every signer and the document's creator

Integrity Verification

The Certificate of Completion embeds the SHA256 hash of the document content immediately before the certificate was appended. If anyone modifies the bytes of the final signed PDF, the certificate's embedded hash will no longer match a freshly computed hash of those bytes — making tampering detectable.

To verify a signed document hasn't been altered:

  1. Download the signed PDF from KarmaFlow
  2. Compute the SHA256 of the file
  3. Compare against the finalPdfSha256 value on the document record (visible via API at GET /internal/document/documents/:id)

Public Signing URLs and Configuration

The public base URL for signing links is controlled by the PUBLIC_BASE_URL (or APP_BASE_URL) environment variable. Set it to whatever public hostname your KarmaFlow instance serves at. The signing-from email address is controlled by SIGNING_FROM_EMAIL and falls back to noreply@{your_mailgun_domain}.

Reminders and Resend

From any document in out_for_signature or partially_signed status, you can click Resend on a specific signer to re-issue their invite. This generates a fresh token (the previous one is invalidated by overwriting the hash) and sends a new email with the new link.

Voiding a Document

You can void any document before it reaches signed status. Voiding:

A fully signed document cannot be voided. To revoke a signed agreement you'd execute a separate mutual termination — same as you would on paper.