Embedded signing
White-label guest signing — your users place fields and sign inside your app, no letssign.now account required.
Embedded signing lets your app start a signing session for one of your end-users (a guest, identified by their email + your own user id). The guest is redirected to a no-login URL on your subdomain, places fields, and signs — or sends to others. The signature is partner-vouched (your app already authenticated them), with optional SMS step-up for AES-grade assurance.
Embedded signing is an Enterprise capability, gated by
embedded_signing_enabled. Talk to sales to enable it for
your workspace and get a sandbox key.
Create a session
Authenticate with your workspace API key. We upsert the guest, ingest the PDF
(by URL or an existing document_id), and return a sessionUrl — redirect
your user's browser there.
Request body
JSON (Content-Type: application/json).
| Field | Type | Required | Description |
|---|---|---|---|
guest | { external_user_id, email, name? } | ✓ | Your end-user. external_user_id is your stable id; one guest per (workspace, id). |
signers | Signer[] | ✓ | 1–20. Each role: "self" (the guest) or "other" (external). |
capability | "place" | "send" | "sign" | Default place. | |
document | { file_url? | document_id? } | A direct PDF URL (≤ 25 MB) or an existing document in your workspace. | |
signing_mode | "parallel" | "sequential" | Default parallel. | |
locale | "en" | "de" | Default en. | |
expires_in_days | int 1..90 | Default 14. | |
redirect_url | string | Where to return the user after signing. |
Signer object
| Field | Type | Required | Description |
|---|---|---|---|
role | "self" | "other" | ✓ | self = the guest signs in-app; other = an external signer gets a request. |
email | string | ✓ | Signer's email. |
name | string | Display name. | |
phone_e164 | string | Required when require_sms is true. | |
require_sms | boolean | SMS step-up → AES-grade for this signer. | |
signing_order | int ≥ 1 | Only when signing_mode="sequential". |
Request
curl -X POST https://api.letssign.now/v1/embedded/sessions \
-H "Authorization: Bearer $LSK_KEY" \
-H "Content-Type: application/json" \
-d '{
"guest": { "external_user_id": "u_8123", "email": "ann@acme.com" },
"capability": "place",
"document": { "file_url": "https://files.acme.com/msa.pdf" },
"signers": [
{ "role": "self", "email": "ann@acme.com", "require_sms": true, "phone_e164": "+41791234567" },
{ "role": "other", "email": "legal@acme.com" }
],
"redirect_url": "https://app.acme.com/contracts/8123/done"
}'Response
HTTP/1.1 201 Created
Content-Type: application/json
{
"sessionId": "9b2e…",
"guestId": "1f4a…",
"capability": "place",
"sessionUrl": "https://acme.letssign.now/en/place/7b1c…",
"expiresAt": "2026-07-01T10:30:00Z",
"signers": [
{ "role": "self", "email": "ann@acme.com" },
{ "role": "other", "email": "legal@acme.com" }
]
}Redirect the browser to sessionUrl. The guest places the fields and hits
Send — their own signature view opens if they're a self signer; other
signers get their invite. Every signing request created is stamped with your
API key + the session id for the audit trail.
sessionUrl is single-use and short-lived. The guest's identity is vouched by
your app; set require_sms on a signer to step up to AES-grade verification.
The assurance level is recorded on the audit trail.
Get session status
/v1/embedded/sessions/{id}{
"id": "9b2e…",
"status": "active",
"capability": "place",
"guestId": "1f4a…",
"target": { "kind": "document", "id": "8a1e…" },
"expiresAt": "2026-07-01T10:30:00Z",
"createdAt": "2026-06-17T10:30:00Z",
"consumedAt": null,
"revokedAt": null
}status: pending → active → consumed (the guest sent) · expired ·
revoked. Or subscribe to webhooks instead of polling.
Revoke a session
/v1/embedded/sessions/{id}Kills the session and its sessionUrl (the link stops working). Idempotent;
returns 409 if the session was already consumed.
200 OK { "ok": true, "status": "revoked" }