Skip to main content
RapidDev - Software Development Agency
App Featuresforms-data22 min read

How to Add Document Signing to Your App (Copy-Paste Prompts Included)

Document signing needs three pieces beyond a signature capture widget: a third-party e-signature API (Dropbox Sign or DocuSign) that creates the legally binding audit trail, a Supabase Edge Function that handles webhooks and keeps your API keys server-side, and precise PDF coordinate mapping so the signature lands on the right spot. With Lovable or V0 you can wire a working signing flow in 3–5 days. Running cost starts at $20–$30/month for the e-signature service; storage and webhooks run on Supabase at $0–$25/month.

4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members

Feature spec

Advanced

Category

forms-data

Build with AI

3–5 days with Lovable + Dropbox Sign/DocuSign API

Custom build

2–4 weeks custom dev

Running cost

$20–$30/mo at launch · $50–$500/mo at scale

Works on

WebMobile

Everything it takes to ship Document Signing — parts, prompts, and real costs.

TL;DR

Document signing needs three pieces beyond a signature capture widget: a third-party e-signature API (Dropbox Sign or DocuSign) that creates the legally binding audit trail, a Supabase Edge Function that handles webhooks and keeps your API keys server-side, and precise PDF coordinate mapping so the signature lands on the right spot. With Lovable or V0 you can wire a working signing flow in 3–5 days. Running cost starts at $20–$30/month for the e-signature service; storage and webhooks run on Supabase at $0–$25/month.

What Document Signing Actually Is

Document signing lets your app collect legally binding electronic signatures on contracts, agreements, and forms. A SaaS startup collects onboarding contracts from new customers without printing anything. A freelance platform has clients sign project agreements before work begins. A real estate app enables lease signing on mobile. The collect-a-signature widget is the easy part — any canvas element can capture a drawn signature. The legally binding part requires an e-signature service (Dropbox Sign or DocuSign) that creates a timestamped, tamper-evident audit trail compliant with the ESIGN Act (US) or eIDAS (EU). Your app calls the API, the service hosts the signing ceremony, and a webhook notifies your app when signing completes — so your API keys never travel to the browser and the legal trail lives in the provider's infrastructure.

What users consider table stakes in 2026

  • Draw, type, or upload a signature on any device including touchscreen — users expect all three input modes with a clear visual distinction between them
  • Signature applied to a specific, visually marked position on the document — not just appended to the bottom of a form, but overlaid at the exact field location in the PDF
  • A legally binding audit trail with signer name, timestamp, IP address, and a tamper-evident document hash — visible to all parties in a downloadable certificate
  • Email notification to all parties when signing is requested and again when it is complete — with a direct link to the signed PDF
  • Multiple sequential signers — sender creates the request, recipient signs, then an optional countersignature from the sender closes the loop
  • A download button for the signed PDF after completion, generating a short-lived signed URL from Supabase Storage so the file is not publicly accessible

Anatomy of the Feature

Six components — the e-signature API integration and the webhook handler are where builds most commonly stall. PDF coordinate mapping and Supabase Storage private bucket setup are the two details that break the first demo.

Layers:UIDataBackendService

Signature capture widget

UI

react-signature-canvas (web) renders a canvas element where users draw their signature with mouse or touch. A tab switcher offers three modes: Draw (canvas), Type (typed name rendered in a Google Fonts cursive font), and Upload (file input accepting PNG/JPG). On mobile Flutter: the hand_signature package or syncfusion_flutter_signaturepad.

Note: The signature captured here is used as the signature image submitted to the e-signature API. For legally binding documents, the provider's hosted signing page (embedded or redirect) is more defensible than a custom-built capture widget — it creates the audit trail automatically.

Document renderer with field placement

UI

react-pdf (pdfjs-dist) renders the base PDF in the browser as a canvas or SVG. Signature fields are overlaid as absolutely-positioned div elements using react-dnd or react-draggable, allowing the document owner to drag and drop signature fields to the exact position. Field coordinates are stored as percentage of page width and height for device-independent positioning.

Note: PDF coordinates use a bottom-left origin in points (72 points per inch), while CSS uses a top-left origin in pixels. Convert on save: pdf_x = (css_x_fraction * page_width_pts). Failing to convert is the most common cause of mispositioned signatures.

E-signature API integration

Service

Dropbox Sign (formerly HelloSign) or DocuSign REST API manages the legally binding signing workflow, audit trail generation, and tamper-evident document sealing. Called exclusively from a Supabase Edge Function — the API key must never be sent to the client. Dropbox Sign's embedded signing flow or redirect to the provider's hosted page both achieve compliance.

Note: Dropbox Sign is easier to integrate and cheaper for low volumes (Essentials $20/mo for 15 docs/mo, approx). DocuSign is the enterprise choice with eIDAS compliance and richer enterprise features (Standard $45/mo, approx). Both offer a free tier: Dropbox Sign 3 docs/mo, DocuSign 30-day trial.

Signed document storage

Backend

Supabase Storage private bucket stores both the unsigned template PDF and the completed signed PDF. The signed version arrives as a download URL in the webhook payload from the e-sign provider after the signing ceremony completes. The signed file is downloaded server-side and re-uploaded to Supabase Storage, then linked in the signing_requests table.

Note: Keep signed documents in a private bucket with RLS — only document owners and named signers should be able to download. Generate short-lived signed URLs (3600 seconds) on demand when the user clicks download.

Signing status + webhook handler

Backend

A Supabase Edge Function receives POST requests from Dropbox Sign or DocuSign when events occur: signature_request_sent, signature_request_signed, signature_request_all_signed, and signature_request_declined. The handler verifies the webhook signature (HMAC), updates the signing_requests and signers tables, downloads the signed PDF on completion, and sends email notification via Resend API.

Note: Webhook signature verification is mandatory — Dropbox Sign sends an X-HelloSign-Signature header with an HMAC-SHA256 hash of the payload. Without verification, any HTTP POST to your webhook URL would be accepted as a valid signing event.

Multi-party orchestration

Data

The signing_requests table tracks document metadata and overall status. The signers table tracks each individual signer with their order_index, status, and signed_at timestamp. When a signer completes, the webhook handler checks whether all previous-order signers are done before sending the invitation to the next signer.

Note: Dropbox Sign handles sequential signing natively via the signers[] array with order indices — the API sends invitations in order automatically. DocuSign calls this routing order. In either case, your database mirrors the state for query purposes, not to drive the workflow.

The data model

Three tables: documents stores the PDF files, signing_requests tracks each signing workflow, and signers tracks each individual signer's status and audit data. The Edge Function uses the service role key for all status updates. Run this in the Supabase SQL editor:

schema.sql
1create table public.documents (
2 id uuid primary key default gen_random_uuid(),
3 owner_id uuid references auth.users(id) on delete cascade not null,
4 name text not null,
5 file_path text not null,
6 signed_file_path text,
7 created_at timestamptz not null default now()
8);
9
10create table public.signing_requests (
11 id uuid primary key default gen_random_uuid(),
12 document_id uuid references public.documents(id) on delete cascade not null,
13 created_by uuid references auth.users(id) on delete set null not null,
14 status text not null default 'pending'
15 check (status in ('pending', 'in_progress', 'completed', 'declined', 'expired')),
16 external_ref text unique,
17 audit_log jsonb not null default '[]',
18 completed_at timestamptz,
19 created_at timestamptz not null default now()
20);
21
22create table public.signers (
23 id uuid primary key default gen_random_uuid(),
24 request_id uuid references public.signing_requests(id) on delete cascade not null,
25 name text not null,
26 email text not null,
27 order_index int not null default 1,
28 status text not null default 'pending'
29 check (status in ('pending', 'sent', 'viewed', 'signed', 'declined')),
30 signed_at timestamptz,
31 ip_address text
32);
33
34create index signing_requests_owner_idx
35 on public.signing_requests (created_by, created_at desc);
36
37create index signers_request_idx
38 on public.signers (request_id, order_index);
39
40alter table public.documents enable row level security;
41alter table public.signing_requests enable row level security;
42alter table public.signers enable row level security;
43
44create policy "Owners can manage their documents"
45 on public.documents for all
46 using (auth.uid() = owner_id);
47
48create policy "Request creator can select signing requests"
49 on public.signing_requests for select
50 using (auth.uid() = created_by);
51
52create policy "Request creator can insert signing requests"
53 on public.signing_requests for insert
54 with check (auth.uid() = created_by);
55
56create policy "Signers can view their own signer row"
57 on public.signers for select
58 using (
59 request_id in (
60 select id from public.signing_requests
61 where created_by = auth.uid()
62 )
63 );

Heads up: The Edge Function webhook handler must use the Supabase service role key (not the anon key) to update signing_requests status and insert into signers after signing events — the RLS policies above do not allow anonymous updates. Store the service role key in the Edge Function's Secrets in the Lovable Cloud tab, never in client-side code. The external_ref column stores the Dropbox Sign or DocuSign signature_request_id for webhook correlation.

Build it — pick your path

Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.

Hand-built by developersFit for this feature:

Required for multi-party enterprise contract workflows, eIDAS Qualified Electronic Signature compliance, or in-person kiosk signing. The only path where the audit trail, PDF sealing, and legal defensibility are production-grade from day one.

Step by step

  1. 1Next.js App Router with DocuSign or Adobe Sign API integration via server-side API routes; Supabase for document storage, status tracking, and webhook handling
  2. 2react-pdf for PDF rendering in the browser; react-dnd for drag-and-drop signature field placement during document setup; coordinate conversion from CSS pixels to PDF points on save
  3. 3HMAC webhook verification with retry handling — failed webhook deliveries from DocuSign retry for 72 hours; the handler must be idempotent (same event processed twice = same result)
  4. 4Audit log completeness review with legal counsel: ensure the signing certificate contains IP address, timestamp, document hash, and provider-issued audit trail before representing the solution as ESIGN Act or eIDAS compliant

Where this path bites

  • Legal review of the audit trail and signing certificate is recommended before using in high-stakes contracts (real estate, employment, financial services) — the technical implementation alone does not guarantee compliance
  • Adobe AATL certificate-based signing (for eIDAS QES) requires a different technical approach and is significantly more expensive than standard API-based signing

Third-party services you'll need

Document signing always requires a paid third-party e-signature service for legal validity. Supabase handles document storage and webhook processing at $0–$25/month:

ServiceWhat it doesFree tierPaid from
Dropbox Sign (HelloSign)E-signature API with legally binding audit trail, ESIGN Act compliance, embedded or hosted signing, and webhook notifications on signing eventsFree (3 documents/mo)Essentials $20/mo (15 docs), Standard $30/mo unlimited (approx)
DocuSignEnterprise e-signature with eIDAS compliance, bulk sending, richer routing options, and Salesforce/enterprise integrationsFree 30-day trialPersonal $15/mo, Standard $45/mo (approx)
SupabasePrivate Storage bucket for unsigned and signed PDFs, Edge Function for webhook handling and API orchestration, RLS-protected document accessFree (500MB DB, 1GB Storage)Pro $25/mo (8GB DB, 100GB Storage)
ResendSigning invitation emails and completion notifications sent from the Supabase Edge Function webhook handlerFree (3,000 emails/mo)Pro $20/mo (50,000 emails/mo)

Swipe the table sideways to see pricing.

What it costs to run

Drag through the tiers to see how your monthly bill scales with users — no surprises later.

Estimated monthly running cost

$25/mo

Dropbox Sign Essentials ($20/mo, approx) for up to 15 documents per month, or DocuSign Personal ($15/mo, approx). Supabase free tier covers document storage and webhook Edge Function. Resend free tier handles notification emails.

Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.

What breaks when AI tools build this

The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.

Webhook from Dropbox Sign or DocuSign never arrives — signing status stays 'pending' indefinitely

Symptom: Lovable's preview pane (*.lovable.app/preview) and V0's sandbox are not stable, publicly accessible URLs. Dropbox Sign and DocuSign require a registered webhook endpoint that responds to HTTP POST requests from the public internet. Preview URLs are either private, behind an iframe, or change between sessions — none of these work as webhook targets.

Fix: Deploy your app to a custom domain or Vercel production environment before registering the webhook URL with the signing provider. In Lovable, use the published app URL (not the preview pane URL). In V0, deploy via the Git panel to Vercel and use the Vercel production URL. Re-register the webhook URL in the Dropbox Sign or DocuSign dashboard pointing to /api/signing-webhook or /functions/v1/signing-webhook.

Signature field appears in the wrong position on the PDF — shifted or offset from where the user placed it

Symptom: PDF coordinates use a bottom-left origin measured in points (72 points per inch), while the browser UI overlay uses a top-left origin in CSS pixels. The page zoom level and DPI scaling add additional offset. AI-generated code commonly passes the raw CSS pixel position directly to the PDF API, which places the signature in the wrong location — often off the page entirely.

Fix: Convert the CSS pixel position to PDF points before sending to the API. For Dropbox Sign: `x_pos = Math.round((css_x / container_width_px) * page_width_pts)` and `y_pos = Math.round(page_height_pts - ((css_y + field_height_px) / container_height_px) * page_height_pts)`. The y-axis inversion is critical — CSS counts from the top, PDF counts from the bottom.

'Request failed with status 403' when calling Dropbox Sign API

Symptom: The Dropbox Sign API key was set as a VITE_HELLOSIGN_API_KEY environment variable (with the VITE_ prefix) and is being called from client-side React code. Dropbox Sign rejects requests from browsers because the API key would be exposed in the browser's network requests to anyone who inspects the app.

Fix: Move the Dropbox Sign API call to a Supabase Edge Function. Store the API key in Lovable's Cloud tab → Secrets (or Vercel's environment variables for V0), accessible via Deno.env.get('HELLOSIGN_API_KEY') in the Edge Function. The Edge Function is called from your client-side code; the API key never leaves the server.

Signed PDF download link expires before the user clicks it

Symptom: Supabase Storage signed URLs have a configurable expiry; AI-generated code commonly calls createSignedUrl() on page mount and passes the resulting URL directly to the download button. If the user stays on the page for more than 60 seconds (the default expiry), the URL has expired by the time they click download — resulting in a 403 or 'The resource you are looking for has been removed' error.

Fix: Generate the Supabase Storage signed URL on button click, not on page mount. Call createSignedUrl(path, 3600) — a 1-hour expiry — at the moment the user clicks the Download button, then open the returned URL immediately. This ensures the URL is always fresh when used.

Best practices

1

Always call the e-signature API from a Supabase Edge Function or Next.js Server Action — never from client-side code where the API key would be visible in browser network requests

2

Verify the webhook HMAC signature header before processing any event — Dropbox Sign sends X-HelloSign-Signature, DocuSign sends X-DocuSign-Signature-1; reject any webhook that fails verification

3

Make your webhook handler idempotent: processing the same event twice should produce the same result without creating duplicate rows or sending duplicate emails — use a unique constraint on external_ref in signing_requests

4

Generate Supabase Storage signed URLs on button click with a 3600-second expiry, not on page load — URLs generated on page load expire before users act on them

5

Store the complete signing event payload from the provider in the audit_log jsonb column for every status change — this becomes the legal record if a signing dispute arises

6

Test the decline flow before launch: what happens if a signer declines is often untested and leaves document owners with a 'pending' status that never resolves

7

Use Dropbox Sign's embedded signing (iFrame mode) for a smoother in-app experience rather than redirecting users to the provider's hosted page — it keeps users in your app while the signing happens

When You Need Custom Development

Lovable and V0 can wire a working Dropbox Sign integration for standard two-party contracts. These requirements push into enterprise territory:

  • Compliance with specific regulations beyond the ESIGN Act: eIDAS Qualified Electronic Signature (QES) using a certificate-based signing method, Adobe AATL certificates, or country-specific requirements in France, Germany, or Switzerland
  • Bulk sending — sending the same contract to 50+ signers simultaneously with per-signer field personalization (name, date, specific clause), which requires a template-based API workflow and queue management
  • In-person signing kiosk mode for retail or field service: a tablet presented to the customer for on-site signature with no email flow, an auto-advance UI, and a receipt-print integration
  • Integration with a contract lifecycle management system (Salesforce CPQ, Ironclad, ContractWorks) for post-signature storage, renewal date tracking, and automated expiry reminders

RapidDev builds these features for production

Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.

Get a fixed-price quote

Frequently asked questions

Is a signature collected with react-signature-canvas legally binding?

A canvas signature alone is not legally binding — it's just an image of a squiggle. Legal validity under the ESIGN Act requires an intent to sign, an audit trail with timestamps and signer identity, and tamper-evident document sealing. That's what e-signature APIs like Dropbox Sign and DocuSign provide. Use react-signature-canvas to capture the signature image, then submit it to the provider's API to create the legally binding record.

What's the difference between Dropbox Sign and DocuSign for a startup?

Dropbox Sign is easier to integrate (API key authentication, simpler endpoint structure), cheaper for low volumes ($20/mo for 15 docs/mo, approx), and has a better developer experience for custom integrations. DocuSign is the enterprise standard: more signers per envelope, eIDAS compliance, Salesforce integration, and a name that enterprise legal teams recognize and trust. Start with Dropbox Sign; migrate to DocuSign when a customer's legal team requires it.

Can multiple people sign the same document in sequence?

Yes. Both Dropbox Sign and DocuSign support sequential signing via an order_index on each signer. The provider sends the signing invitation to signer 1 first; only after they complete (or the flow reaches their position in the sequence) does the invitation go to signer 2. Your database mirrors this state in the signers table via webhook events.

How do I store signed PDFs securely in Supabase?

Use a private Supabase Storage bucket with RLS policies that allow only the document owner and named signers to access files. Download the signed PDF from the provider's API in your webhook handler (server-side, using the service role key), upload it to Supabase Storage, and store the path in signing_requests.signed_file_path. Generate short-lived signed URLs (3600 seconds) on demand when a user clicks download — never store or display public URLs to private documents.

Why does my webhook handler not receive events from DocuSign?

The most common causes: (1) The webhook URL is a preview or local URL — both Dropbox Sign and DocuSign require a publicly accessible HTTPS endpoint, so you must deploy to production before registering the webhook. (2) The HMAC signature verification is failing and your handler returns a 400 before processing — check the verification logic against the provider's documentation. (3) The URL was registered with a trailing slash that doesn't match the route.

How do I add a signature field to a specific location on a PDF?

Render the PDF with react-pdf (pdfjs-dist), overlay absolutely-positioned div elements for signature fields, and let users drag them to the desired position. Store field coordinates as percentages of the page dimensions. Before sending to Dropbox Sign or DocuSign, convert to PDF points: x_pts = (x_fraction * page_width_pts) and y_pts = page_height_pts - (y_fraction * page_height_pts). The y-axis is inverted because PDF coordinates start at the bottom-left.

Can FlutterFlow handle document signing on mobile?

FlutterFlow can capture a signature image using a signature pad widget and upload it to Supabase. For a standalone signature-as-form-field use case (like collecting agreement at the end of an intake form), that's sufficient. For true document signing — where the signature appears at a specific position in a PDF with a legal audit trail — FlutterFlow cannot render a PDF with overlaid fields natively. A WebView pointing to a Dropbox Sign embedded signing URL is the practical workaround.

How much does it cost to add e-signatures to my app?

Budget $20–$30/month to start: Dropbox Sign Essentials ($20/mo, approx) covers 15 signed documents per month, which is enough for most early-stage apps. Supabase handles document storage on its free tier. As volume grows, Dropbox Sign Standard ($30/mo, approx) is unlimited documents. Enterprise volumes require custom pricing from DocuSign or Dropbox Sign. The signature capture widget itself (react-signature-canvas) is free.

RapidDev

Need this feature production-ready?

RapidDev builds document signing into real apps — auth, database, payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.