How to add e-signatures to your app: API, embedded signing and webhooks
Add e-signatures to your app: create envelopes with a REST API, embed signing in your product, and confirm with signed webhooks. With Node, Python and React.
If your product involves agreements, such as onboarding contracts, rental applications, consent forms or order forms, sending users off to another site to sign is a leaky step in your funnel. Embedding signing keeps them in your product, and a decent API means you can do it in an afternoon. This guide walks through the whole flow with Parapho; the same concepts apply to most e-signature APIs.
The flow at a glance
- Template: upload your PDF once and place fields for each signer role.
- Envelope: your server creates an envelope from the template for a specific person.
- Embed: your page shows that person's signing link in a frame.
- Events: the browser tells your page when they finish; a signed webhook tells your server.
- Download: fetch the sealed PDF and store it, or link to it.
1. Build a template
In the app, upload the PDF and drag fields onto it: signature, initials, date, name, text, number, checkbox, dropdown and more. Give each field a role, such as Client or Company. You can also upload documents and create templates over the API. Templates mean your code sends only who signs, never where fields go.
2. Get API keys and allow your origin
Under Settings & API, create an API key. Start with a sk_test_ key: test envelopes are watermarked, no email is sent and nothing is billed. Keys are shown once and stored only as hashes, so put yours in an environment variable.
Then add each site that will host the signing frame under Embedded signing → Allowed origins, for example https://app.yourcompany.com. Origins are scheme, host and port only. Until an origin is added, browsers refuse to show the frame.
3. Create the envelope on your server
Set send_email: false so Parapho doesn't email the signer, and send an Idempotency-Key so a retried request can't create a duplicate.
import { Parapho } from '@parapho/node';
const parapho = new Parapho({ apiKey: process.env.PARAPHO_API_KEY });
const envelope = await parapho.envelopes.create({
template_id: 'tpl_nda',
send_email: false, // we'll embed signing instead
recipients: [
{ role: 'Client', name: 'Ada Lovelace', email: 'ada@example.com' },
],
}, { idempotencyKey: `nda-${dealId}` });
const embedUrl = envelope.recipients[0].embed_url;
Or in Python:
import os
from parapho import Parapho
parapho = Parapho(api_key=os.environ["PARAPHO_API_KEY"])
envelope = parapho.envelopes.create(
"tpl_nda",
[{"role": "Client", "name": "Ada Lovelace", "email": "ada@example.com"}],
send_email=False,
idempotency_key=f"nda-{deal_id}",
)
You can also prefill fields (editable or read-only), attach your own metadata such as a customer ID, and choose sequential or parallel signing for several recipients.
4. Show signing in your page
Load the embed script and drop in the web component. The frame resizes itself to the document.
<script src="https://app.parapho.com/sdk/embed.js"></script>
<parapho-signing src="EMBED_URL"></parapho-signing>
<script>
document.querySelector('parapho-signing')
.addEventListener('parapho:completed', (e) => {
showThankYou(e.detail.envelopeId);
});
</script>
In React:
import { ParaphoSigning } from '@parapho/react';
export function SignContract({ embedUrl }) {
return (
<ParaphoSigning
url={embedUrl}
onComplete={({ envelopeId }) => router.push(`/done/${envelopeId}`)}
onDecline={() => toast('The signer declined')}
/>
);
}
5. Trust the webhook, not the browser
Browser events are great for UX, but a user can close the tab or tamper with client-side code. Update your database from the webhook instead. Every webhook carries a Parapho-Signature header (HMAC-SHA256 with a timestamp), and the SDKs verify it in one line:
import express from 'express';
import { verifyWebhook } from '@parapho/node';
app.post('/webhooks/parapho', express.raw({ type: '*/*' }), (req, res) => {
const event = verifyWebhook(
req.body, // the raw body
req.get('parapho-signature'),
process.env.PARAPHO_WEBHOOK_SECRET,
);
if (event.event === 'envelope.completed') {
markContractSigned(event.data.id);
}
res.sendStatus(200);
});
The events you'll use most:
| Event | When |
|---|---|
envelope.sent | The envelope was created |
recipient.viewed | A recipient opened the document on their turn |
recipient.signed | A recipient signed and others still need to |
envelope.completed | Everyone signed; the sealed PDF is ready and data.final_sha256 is set |
envelope.declined | A recipient declined |
envelope.voided / envelope.expired | Cancelled, or the deadline passed |
You can register up to 10 webhook endpoints per workspace, each with its own event filter and secret. Failed deliveries are retried automatically for about a day, and the delivery log lets you inspect and resend any event.
6. Download the sealed PDF
Once complete, GET /api/v1/envelopes/:id/download returns the flattened, digitally signed PDF with its certificate of completion. You can also read every field value as JSON, which is handy for pushing form answers into your CRM. Store the final_sha256 alongside it so you can prove later that the file is unchanged.
Going live
- Swap your
sk_test_key for a live key. - Add your production origin to the allowed origins (https only).
- Point a webhook endpoint at production and store its secret.
- Handle
envelope.declinedandenvelope.expired, not just the happy path.
API pricing is $0.02 per completed envelope, with the first 100 free every month and no minimum. The developer docs have the full API reference, more recipes and a go-live checklist, or you can get API keys now.
Frequently asked questions
What's the difference between embedded signing and email signing?
With email signing, the platform emails each signer a link and handles reminders. With embedded signing, your app creates the envelope with email turned off and shows the signing page inside your own product in a frame, so the signer never leaves your site.
How do I know when a document has been signed?
Subscribe to webhooks. Parapho sends envelope.completed when everyone has signed and the sealed PDF is ready, with the final SHA-256 fingerprint. Verify the Parapho-Signature header before trusting the payload.
Can I test without sending real emails or being charged?
Yes. Test keys starting with sk_test_ create sandboxed, watermarked envelopes. No emails are sent and nothing is billed.
How do I avoid creating duplicate envelopes when a request is retried?
Send an Idempotency-Key header with a value unique to the business action, such as a deal ID. A retry with the same key returns the original response instead of creating a second envelope.
Which languages have SDKs?
Parapho has an embed script and web component for any website, a React component, a Node.js SDK and a Python SDK. Any language can call the REST API directly.
This article is general information, not legal advice. Rules differ by country, state and document type; check with a lawyer for anything high-stakes.