Recipes
End-to-end integration patterns — Portant, Zapier, agent-generated PDFs, CRM sync.
Common shapes integrators ship. Each is a real call sequence with
the corner cases called out. Every URL is on the canonical host
https://api.wesign.now/v1; the api.letssign.now alias works
identically.
Document automation tool → letssign.now
Pattern: Your Portant / DocAssemble / Pandoc / LaTeX pipeline generates the PDF; letssign.now sends it for signature.
Embed anchors at template authoring time
Drop [[ls:signature:role]] tokens in the template:
<!-- HTML→PDF template -->
<p>Tenant signature: [[ls:signature:tenant]]</p>
<p>Date: [[ls:date:tenant]]</p>The role is your business-domain identifier. Pick something stable;
you'll reuse it as signers[].role.
Render the PDF
Whatever your generator is, write the bytes to disk or memory. Don't flatten — the marker needs to remain in the text layer.
POST to the endpoint
curl -X POST https://api.wesign.now/v1/signing-requests \
-H "Authorization: Bearer $WSK_KEY" \
-H "Idempotency-Key: ${SOURCE_DOC_ID}" \
-F "file=@/tmp/contract.pdf" \
-F 'signers=[
{"email":"tenant@example.com", "role":"tenant"},
{"email":"landlord@example.com","role":"landlord", "phone_e164":"+41791112233", "require_sms_verification":true}
]' \
-F 'callback_url=https://yourapp.com/wesign/callback'Use your source-document ID as the Idempotency-Key. Re-running the
same template against the same row replays cleanly. The landlord above
has to confirm an SMS code before signing — see
Second factor by SMS.
Receive the signed PDF
Verify the webhook signature (see Webhooks), then
fetch signed_pdf_url with your API key — it is
https://api.wesign.now/v1/documents/{id}/signed, so pin the fetch
to api.wesign.now — and fetch audit_trail_url the same way (same
host, same key: …/documents/{id}/audit-trail), then archive both
into your DMS.
Multi-tenant platform (one key, many clients)
Pattern: You run a platform for many firms and send on their behalf.
- One workspace API key covers every document the workspace sends;
hooks are per workspace, so one dashboard-registered webhook with
one stable secret receives everything. Route on
document_id(or on an id you keep in your own table keyed bydocumentIdfrom the201). - If a client needs its own brand on the signing page, that is a workspace of its own with its own key — brand, hosting region and reply-to all hang off the key.
- Pin outbound fetches to
api.wesign.now(orapi.letssign.nowif you deliberately use the alias); thesigned_pdf_urlfield only ever names the canonical host.
Zapier / no-code triggers
Pattern: A Google Form / Typeform / Notion entry triggers a signing.
Without an SDK you can stitch this with Zapier's "Webhooks by Zapier" action:
- Trigger: form submission.
- Action 1: pull the PDF (Google Drive download).
- Action 2: POST to
https://api.wesign.now/v1/signing-requestswith a multipart body. Zapier's Webhooks step supports binary uploads via the Custom Request advanced mode. - Action 3: log the response in a Google Sheet for audit.
Most no-code platforms throttle long-running steps — the multipart
upload may take >10s for large PDFs. The endpoint is idempotent under
the same Idempotency-Key, so a retry from Zapier's built-in
error handler is safe.
Agent-generated PDFs
Pattern: An AI agent (Claude tool-use, OpenAI function-calling, LangChain/LangGraph) produces a draft contract and ships it for signature without human-in-the-loop review.
Have the agent emit role-tagged anchors — [[ls:signature:client]] —
into the draft and send with placement="anchors". The anchor is
placed where the signature belongs by construction, so there is no
"signature box on page 3 paragraph 4" failure mode. For fully
programmatic layouts, placement="explicit" takes exact coordinates.
placement="manual"(a human-review placement URL) was retired on 2026-07-31 — requests using it receive400 placement_retired. Keep the human in the loop before the send instead: generate, review, then dispatch with anchors — or stage a template instance for review.
CRM sync (Salesforce / HubSpot / Attio)
Pattern: A Closed-Won opportunity triggers a contract send; contract status updates back to the CRM record.
- Webhook from CRM fires when stage flips to "Closed-Won".
- Your handler builds the PDF (template + opportunity field values).
- POST to
https://api.wesign.now/v1/signing-requestswithcallback_urlpointing back at your handler. - On
signing_request.signed, update the CRM custom fieldsigned_at. Ondocument.completed, archive the signed PDF + the audit trail in the CRM Notes / Files tab.
X-WeSign-Event-Id is your dedup key on the receiver side — at-
least-once delivery means retries occasionally re-fire the same event.
(The same value also arrives as X-LetsSign-Event-Id.)
Building your own
Three rules of thumb:
- Always pass an Idempotency-Key on the create calls —
POST /v1/signing-requestsandPOST /v1/templates/{id}/instantiate./reminddoes not take one and re-sends on every call, so debounce it yourself. - Verify webhook signatures. Skipping this is the #1 incident
pattern; an attacker can forge
signing_request.signedand your handler updates a CRM with bogus state. Node and PHP snippets are in Verifying a signature. - Cache the signed PDF locally. Don't re-fetch from
/v1/documents/{id}/signedon every dashboard view; pull once ondocument.completedand store.
